diff --git a/tests/backend/fuzzImportTest.js b/tests/backend/fuzzImportTest.js new file mode 100644 index 000000000..1c82e6c1c --- /dev/null +++ b/tests/backend/fuzzImportTest.js @@ -0,0 +1,76 @@ +/* + * Fuzz testing the import endpoint + * Usage: node fuzzImportTest.js + */ +const fs = require('fs'); +const settings = require(__dirname+'/loadSettings').loadSettings(); +const host = "http://" + settings.ip + ":" + settings.port; +const path = require('path'); +const async = require(__dirname+'/../../src/node_modules/async'); +const request = require('request'); +const froth = require('mocha-froth'); + +var filePath = path.join(__dirname, '/../../APIKEY.txt'); +var apiKey = fs.readFileSync(filePath, {encoding: 'utf-8'}); +apiKey = apiKey.replace(/\n$/, ""); +var apiVersion = 1; +var testPadId = "TEST_fuzz" + makeid(); + +var endPoint = function(point, version){ + version = version || apiVersion; + return '/api/'+version+'/'+point+'?apikey='+apiKey; +} + +console.log("Testing against padID", testPadId); +console.log("To watch the test live visit " + host + "/p/" + testPadId); +console.log("Tests will start in 5 seconds, click the URL now!"); + +setTimeout(function(){ + for (let i=1; i<1000000; i++) { // 1M runs + setTimeout( function timer(){ + runTest(i); + }, i*100 ); // 100 ms + } +},5000); // wait 5 seconds + +function runTest(number){ + request(host + endPoint('createPad') + '&padID=' + testPadId, function(err, res, body){ + var req = request.post(host + '/p/'+testPadId+'/import', function (err, res, body) { + if (err) { + throw new Error("FAILURE", err); + }else{ + console.log("Success"); + } + }); + + var fN = '/test.txt'; + var cT = 'text/plain'; + + // To be more agressive every other test we mess with Etherpad + // We provide a weird file name and also set a weird contentType + if (number % 2 == 0) { + fN = froth().toString(); + cT = froth().toString(); + } + + let form = req.form(); + form.append('file', froth().toString(), { + filename: fN, + contentType: cT + }); + + }); +} + +function makeid() +{ + var text = ""; + var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + + for( var i=0; i < 5; i++ ){ + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; +} + + diff --git a/tests/backend/loadSettings.js b/tests/backend/loadSettings.js deleted file mode 100644 index f208fe3d0..000000000 --- a/tests/backend/loadSettings.js +++ /dev/null @@ -1,17 +0,0 @@ -var jsonminify = require(__dirname+"/../../src/node_modules/jsonminify"); - -function loadSettings(){ - var settingsStr = fs.readFileSync(__dirname+"/../../settings.json").toString(); - // try to parse the settings - var settings; - try { - if(settingsStr) { - settingsStr = jsonminify(settingsStr).replace(",]","]").replace(",}","}"); - return JSON.parse(settingsStr); - } - }catch(e){ - console.error("whoops something is bad with settings"); - } -} - -exports.loadSettings = loadSettings; diff --git a/tests/backend/specs/api/api.js b/tests/backend/specs/api/api.js new file mode 100644 index 000000000..0b372de85 --- /dev/null +++ b/tests/backend/specs/api/api.js @@ -0,0 +1,79 @@ +/** + * API specs + * + * Tests for generic overarching HTTP API related features not related to any + * specific part of the data model or domain. For example: tests for versioning + * and openapi definitions. + */ + +const assert = require('assert'); +const supertest = require(__dirname + '/../../../../src/node_modules/supertest'); +const fs = require('fs'); +const settings = require(__dirname + '/../../../../src/node/utils/Settings'); +const api = supertest('http://' + settings.ip + ':' + settings.port); +const path = require('path'); + +var validateOpenAPI = require(__dirname + '/../../../../src/node_modules/openapi-schema-validation').validate; + +var filePath = path.join(__dirname, '../../../../APIKEY.txt'); + +var apiKey = fs.readFileSync(filePath, { encoding: 'utf-8' }); +apiKey = apiKey.replace(/\n$/, ''); +var apiVersion = 1; + +var testPadId = makeid(); + +describe('API Versioning', function() { + it('errors if can not connect', function(done) { + api + .get('/api/') + .expect(function(res) { + apiVersion = res.body.currentVersion; + if (!res.body.currentVersion) throw new Error('No version set in API'); + return; + }) + .expect(200, done); + }); +}); + +describe('OpenAPI definition', function() { + it('generates valid openapi definition document', function(done) { + api + .get('/api/openapi.json') + .expect(function(res) { + const { valid, errors } = validateOpenAPI(res.body, 3); + if (!valid) { + const prettyErrors = JSON.stringify(errors, null, 2); + throw new Error(`Document is not valid OpenAPI. ${errors.length} validation errors:\n${prettyErrors}`); + } + return; + }) + .expect(200, done); + }); +}); + +describe('jsonp support', function() { + it('supports jsonp calls', function(done) { + api + .get(endPoint('createPad') + '&jsonp=jsonp_1&padID=' + testPadId) + .expect(function(res) { + if (!res.text.match('jsonp_1')) throw new Error('no jsonp call seen'); + }) + .expect('Content-Type', /javascript/) + .expect(200, done); + }); +}); + +var endPoint = function(point) { + return '/api/' + apiVersion + '/' + point + '?apikey=' + apiKey; +}; + +function makeid() { + var text = ''; + var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + + for (var i = 0; i < 5; i++) { + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; +} diff --git a/tests/backend/specs/api/characterEncoding.js b/tests/backend/specs/api/characterEncoding.js new file mode 100644 index 000000000..83145089f --- /dev/null +++ b/tests/backend/specs/api/characterEncoding.js @@ -0,0 +1,113 @@ +/* + * This file is copied & modified from /tests/backend/specs/api/pad.js + * + * TODO: maybe unify those two files and merge in a single one. + */ + +const assert = require('assert'); +const supertest = require(__dirname+'/../../../../src/node_modules/supertest'); +const fs = require('fs'); +const settings = require(__dirname + '/../../../../src/node/utils/Settings'); +const api = supertest('http://'+settings.ip+":"+settings.port); +const path = require('path'); +const async = require(__dirname+'/../../../../src/node_modules/async'); + +var filePath = path.join(__dirname, '../../../../APIKEY.txt'); + +var apiKey = fs.readFileSync(filePath, {encoding: 'utf-8'}); +apiKey = apiKey.replace(/\n$/, ""); +var apiVersion = 1; +var testPadId = makeid(); + +describe('Connectivity For Character Encoding', function(){ + it('can connect', function(done) { + api.get('/api/') + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('API Versioning', function(){ + it('finds the version tag', function(done) { + api.get('/api/') + .expect(function(res){ + apiVersion = res.body.currentVersion; + if (!res.body.currentVersion) throw new Error("No version set in API"); + return; + }) + .expect(200, done) + }); +}) + +describe('Permission', function(){ + it('errors with invalid APIKey', function(done) { + // This is broken because Etherpad doesn't handle HTTP codes properly see #2343 + // If your APIKey is password you deserve to fail all tests anyway + var permErrorURL = '/api/'+apiVersion+'/createPad?apikey=password&padID=test'; + api.get(permErrorURL) + .expect(401, done) + }); +}) + +describe('createPad', function(){ + it('creates a new Pad', function(done) { + api.get(endPoint('createPad')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Unable to create new Pad"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('setHTML', function(){ + it('Sets the HTML of a Pad attempting to weird utf8 encoded content', function(done) { + fs.readFile('../tests/backend/specs/api/emojis.html', 'utf8', function(err, html) { + api.post(endPoint('setHTML')) + .send({ + "padID": testPadId, + "html": html, + }) + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Can't set HTML properly"); + }) + .expect('Content-Type', /json/) + .expect(200, done); + }); + }); +}) + +describe('getHTML', function(){ + it('get the HTML of Pad with emojis', function(done) { + api.get(endPoint('getHTML')+"&padID="+testPadId) + .expect(function(res){ + if (res.body.data.html.indexOf("🇼") === -1) { + throw new Error("Unable to get the HTML"); + } + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +/* + + End of test + +*/ + +var endPoint = function(point, version){ + version = version || apiVersion; + return '/api/'+version+'/'+point+'?apikey='+apiKey; +} + +function makeid() +{ + var text = ""; + var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + + for( var i=0; i < 10; i++ ){ + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; +} diff --git a/tests/backend/specs/api/chat.js b/tests/backend/specs/api/chat.js index 2bcd9783a..e2ce59b1c 100644 --- a/tests/backend/specs/api/chat.js +++ b/tests/backend/specs/api/chat.js @@ -1,7 +1,7 @@ var assert = require('assert') supertest = require(__dirname+'/../../../../src/node_modules/supertest'), fs = require('fs'), - settings = require(__dirname+'/../../loadSettings').loadSettings(), + settings = require(__dirname + '/../../../../src/node/utils/Settings'), api = supertest('http://'+settings.ip+":"+settings.port), path = require('path'); diff --git a/tests/backend/specs/api/emojis.html b/tests/backend/specs/api/emojis.html new file mode 100644 index 000000000..a0e3d2752 --- /dev/null +++ b/tests/backend/specs/api/emojis.html @@ -0,0 +1,10 @@ + + + + foo + + + +๐Ÿ˜€ ๐Ÿ˜ ๐Ÿ˜‚ ๐Ÿคฃ ๐Ÿ˜ƒ ๐Ÿ˜„ ๐Ÿ˜… ๐Ÿ˜† ๐Ÿ˜‰ ๐Ÿ˜Š ๐Ÿ˜‹ ๐Ÿ˜Ž ๐Ÿ˜ ๐Ÿ˜˜ ๐Ÿฅฐ ๐Ÿ˜— ๐Ÿ˜™ ๐Ÿ˜š โ˜บ๏ธ ๐Ÿ™‚ ๐Ÿค— ๐Ÿคฉ ๐Ÿค” ๐Ÿคจ ๐Ÿ˜ ๐Ÿ˜‘ ๐Ÿ˜ถ ๐Ÿ™„ ๐Ÿ˜ ๐Ÿ˜ฃ ๐Ÿ˜ฅ ๐Ÿ˜ฎ ๐Ÿค ๐Ÿ˜ฏ ๐Ÿ˜ช ๐Ÿ˜ซ ๐Ÿ˜ด ๐Ÿ˜Œ ๐Ÿ˜› ๐Ÿ˜œ ๐Ÿ˜ ๐Ÿคค ๐Ÿ˜’ ๐Ÿ˜“ ๐Ÿ˜” ๐Ÿ˜• ๐Ÿ™ƒ ๐Ÿค‘ ๐Ÿ˜ฒ โ˜น๏ธ ๐Ÿ™ ๐Ÿ˜– ๐Ÿ˜ž ๐Ÿ˜Ÿ ๐Ÿ˜ค ๐Ÿ˜ข ๐Ÿ˜ญ ๐Ÿ˜ฆ ๐Ÿ˜ง ๐Ÿ˜จ ๐Ÿ˜ฉ ๐Ÿคฏ ๐Ÿ˜ฌ ๐Ÿ˜ฐ ๐Ÿ˜ฑ ๐Ÿฅต ๐Ÿฅถ ๐Ÿ˜ณ ๐Ÿคช ๐Ÿ˜ต ๐Ÿ˜ก ๐Ÿ˜  ๐Ÿคฌ ๐Ÿ˜ท ๐Ÿค’ ๐Ÿค• ๐Ÿคข ๐Ÿคฎ ๐Ÿคง ๐Ÿ˜‡ ๐Ÿค  ๐Ÿคก ๐Ÿฅณ ๐Ÿฅด ๐Ÿฅบ ๐Ÿคฅ ๐Ÿคซ ๐Ÿคญ ๐Ÿง ๐Ÿค“ ๐Ÿ˜ˆ ๐Ÿ‘ฟ ๐Ÿ‘น ๐Ÿ‘บ ๐Ÿ’€ ๐Ÿ‘ป ๐Ÿ‘ฝ ๐Ÿค– ๐Ÿ’ฉ ๐Ÿ˜บ ๐Ÿ˜ธ ๐Ÿ˜น ๐Ÿ˜ป ๐Ÿ˜ผ ๐Ÿ˜ฝ ๐Ÿ™€ ๐Ÿ˜ฟ ๐Ÿ˜พ ๐Ÿ‘ถ ๐Ÿ‘ง ๐Ÿง’ ๐Ÿ‘ฆ ๐Ÿ‘ฉ ๐Ÿง‘ ๐Ÿ‘จ ๐Ÿ‘ต ๐Ÿง“ ๐Ÿ‘ด ๐Ÿ‘ฒ ๐Ÿ‘ณโ€โ™€๏ธ ๐Ÿ‘ณโ€โ™‚๏ธ ๐Ÿง• ๐Ÿง” ๐Ÿ‘ฑโ€โ™‚๏ธ ๐Ÿ‘ฑโ€โ™€๏ธ ๐Ÿ‘จโ€๐Ÿฆฐ ๐Ÿ‘ฉโ€๐Ÿฆฐ ๐Ÿ‘จโ€๐Ÿฆฑ ๐Ÿ‘ฉโ€๐Ÿฆฑ ๐Ÿ‘จโ€๐Ÿฆฒ ๐Ÿ‘ฉโ€๐Ÿฆฒ ๐Ÿ‘จโ€๐Ÿฆณ ๐Ÿ‘ฉโ€๐Ÿฆณ ๐Ÿฆธโ€โ™€๏ธ ๐Ÿฆธโ€โ™‚๏ธ ๐Ÿฆนโ€โ™€๏ธ ๐Ÿฆนโ€โ™‚๏ธ ๐Ÿ‘ฎโ€โ™€๏ธ ๐Ÿ‘ฎโ€โ™‚๏ธ ๐Ÿ‘ทโ€โ™€๏ธ ๐Ÿ‘ทโ€โ™‚๏ธ ๐Ÿ’‚โ€โ™€๏ธ ๐Ÿ’‚โ€โ™‚๏ธ ๐Ÿ•ต๏ธโ€โ™€๏ธ ๐Ÿ•ต๏ธโ€โ™‚๏ธ ๐Ÿ‘ฉโ€โš•๏ธ ๐Ÿ‘จโ€โš•๏ธ ๐Ÿ‘ฉโ€๐ŸŒพ ๐Ÿ‘จโ€๐ŸŒพ ๐Ÿ‘ฉโ€๐Ÿณ ๐Ÿ‘จโ€๐Ÿณ ๐Ÿ‘ฉโ€๐ŸŽ“ ๐Ÿ‘จโ€๐ŸŽ“ ๐Ÿ‘ฉโ€๐ŸŽค ๐Ÿ‘จโ€๐ŸŽค ๐Ÿ‘ฉโ€๐Ÿซ ๐Ÿ‘จโ€๐Ÿซ ๐Ÿ‘ฉโ€๐Ÿญ ๐Ÿ‘จโ€๐Ÿญ ๐Ÿ‘ฉโ€๐Ÿ’ป ๐Ÿ‘จโ€๐Ÿ’ป ๐Ÿ‘ฉโ€๐Ÿ’ผ ๐Ÿ‘จโ€๐Ÿ’ผ ๐Ÿ‘ฉโ€๐Ÿ”ง ๐Ÿ‘จโ€๐Ÿ”ง ๐Ÿ‘ฉโ€๐Ÿ”ฌ ๐Ÿ‘จโ€๐Ÿ”ฌ ๐Ÿ‘ฉโ€๐ŸŽจ ๐Ÿ‘จโ€๐ŸŽจ ๐Ÿ‘ฉโ€๐Ÿš’ ๐Ÿ‘จโ€๐Ÿš’ ๐Ÿ‘ฉโ€โœˆ๏ธ ๐Ÿ‘จโ€โœˆ๏ธ ๐Ÿ‘ฉโ€๐Ÿš€ ๐Ÿ‘จโ€๐Ÿš€ ๐Ÿ‘ฉโ€โš–๏ธ ๐Ÿ‘จโ€โš–๏ธ ๐Ÿ‘ฐ ๐Ÿคต ๐Ÿ‘ธ ๐Ÿคด ๐Ÿคถ ๐ŸŽ… ๐Ÿง™โ€โ™€๏ธ ๐Ÿง™โ€โ™‚๏ธ ๐Ÿงโ€โ™€๏ธ ๐Ÿงโ€โ™‚๏ธ ๐Ÿง›โ€โ™€๏ธ ๐Ÿง›โ€โ™‚๏ธ ๐ŸงŸโ€โ™€๏ธ ๐ŸงŸโ€โ™‚๏ธ ๐Ÿงžโ€โ™€๏ธ ๐Ÿงžโ€โ™‚๏ธ ๐Ÿงœโ€โ™€๏ธ ๐Ÿงœโ€โ™‚๏ธ ๐Ÿงšโ€โ™€๏ธ ๐Ÿงšโ€โ™‚๏ธ ๐Ÿ‘ผ ๐Ÿคฐ ๐Ÿคฑ ๐Ÿ™‡โ€โ™€๏ธ ๐Ÿ™‡โ€โ™‚๏ธ ๐Ÿ’โ€โ™€๏ธ ๐Ÿ’โ€โ™‚๏ธ ๐Ÿ™…โ€โ™€๏ธ ๐Ÿ™…โ€โ™‚๏ธ ๐Ÿ™†โ€โ™€๏ธ ๐Ÿ™†โ€โ™‚๏ธ ๐Ÿ™‹โ€โ™€๏ธ ๐Ÿ™‹โ€โ™‚๏ธ ๐Ÿคฆโ€โ™€๏ธ ๐Ÿคฆโ€โ™‚๏ธ ๐Ÿคทโ€โ™€๏ธ ๐Ÿคทโ€โ™‚๏ธ ๐Ÿ™Žโ€โ™€๏ธ ๐Ÿ™Žโ€โ™‚๏ธ ๐Ÿ™โ€โ™€๏ธ ๐Ÿ™โ€โ™‚๏ธ ๐Ÿ’‡โ€โ™€๏ธ ๐Ÿ’‡โ€โ™‚๏ธ ๐Ÿ’†โ€โ™€๏ธ ๐Ÿ’†โ€โ™‚๏ธ ๐Ÿง–โ€โ™€๏ธ ๐Ÿง–โ€โ™‚๏ธ ๐Ÿ’… ๐Ÿคณ ๐Ÿ’ƒ ๐Ÿ•บ ๐Ÿ‘ฏโ€โ™€๏ธ ๐Ÿ‘ฏโ€โ™‚๏ธ ๐Ÿ•ด ๐Ÿšถโ€โ™€๏ธ ๐Ÿšถโ€โ™‚๏ธ ๐Ÿƒโ€โ™€๏ธ ๐Ÿƒโ€โ™‚๏ธ ๐Ÿ‘ซ ๐Ÿ‘ญ ๐Ÿ‘ฌ ๐Ÿ’‘ ๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ‘ฉ ๐Ÿ‘จโ€โค๏ธโ€๐Ÿ‘จ ๐Ÿ’ ๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ ๐Ÿ‘จโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ ๐Ÿ‘ช ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ง ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง ๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ ๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ง ๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ ๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ ๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง ๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ฆ ๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ง ๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฆ ๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ ๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ง ๐Ÿ‘ฉโ€๐Ÿ‘ฆ ๐Ÿ‘ฉโ€๐Ÿ‘ง ๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ ๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ ๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง ๐Ÿ‘จโ€๐Ÿ‘ฆ ๐Ÿ‘จโ€๐Ÿ‘ง ๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฆ ๐Ÿ‘จโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ ๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ง ๐Ÿคฒ ๐Ÿ‘ ๐Ÿ™Œ ๐Ÿ‘ ๐Ÿค ๐Ÿ‘ ๐Ÿ‘Ž ๐Ÿ‘Š โœŠ ๐Ÿค› ๐Ÿคœ ๐Ÿคž โœŒ๏ธ ๐ŸคŸ ๐Ÿค˜ ๐Ÿ‘Œ ๐Ÿ‘ˆ ๐Ÿ‘‰ ๐Ÿ‘† ๐Ÿ‘‡ โ˜๏ธ โœ‹ ๐Ÿคš ๐Ÿ– ๐Ÿ–– ๐Ÿ‘‹ ๐Ÿค™ ๐Ÿ’ช ๐Ÿฆต ๐Ÿฆถ ๐Ÿ–• โœ๏ธ ๐Ÿ™ ๐Ÿ’ ๐Ÿ’„ ๐Ÿ’‹ ๐Ÿ‘„ ๐Ÿ‘… ๐Ÿ‘‚ ๐Ÿ‘ƒ ๐Ÿ‘ฃ ๐Ÿ‘ ๐Ÿ‘€ ๐Ÿง  ๐Ÿฆด ๐Ÿฆท ๐Ÿ—ฃ ๐Ÿ‘ค ๐Ÿ‘ฅ๐Ÿงฅ ๐Ÿ‘š ๐Ÿ‘• ๐Ÿ‘– ๐Ÿ‘” ๐Ÿ‘— ๐Ÿ‘™ ๐Ÿ‘˜ ๐Ÿ‘  ๐Ÿ‘ก ๐Ÿ‘ข ๐Ÿ‘ž ๐Ÿ‘Ÿ ๐Ÿฅพ ๐Ÿฅฟ ๐Ÿงฆ ๐Ÿงค ๐Ÿงฃ ๐ŸŽฉ ๐Ÿงข ๐Ÿ‘’ ๐ŸŽ“ โ›‘ ๐Ÿ‘‘ ๐Ÿ‘ ๐Ÿ‘› ๐Ÿ‘œ ๐Ÿ’ผ ๐ŸŽ’ ๐Ÿ‘“ ๐Ÿ•ถ ๐Ÿฅฝ ๐Ÿฅผ ๐ŸŒ‚ ๐Ÿงต ๐Ÿงถ๐Ÿ‘ถ๐Ÿป ๐Ÿ‘ฆ๐Ÿป ๐Ÿ‘ง๐Ÿป ๐Ÿ‘จ๐Ÿป ๐Ÿ‘ฉ๐Ÿป ๐Ÿ‘ฑ๐Ÿปโ€โ™€๏ธ ๐Ÿ‘ฑ๐Ÿป ๐Ÿ‘ด๐Ÿป ๐Ÿ‘ต๐Ÿป ๐Ÿ‘ฒ๐Ÿป ๐Ÿ‘ณ๐Ÿปโ€โ™€๏ธ ๐Ÿ‘ณ๐Ÿป ๐Ÿ‘ฎ๐Ÿปโ€โ™€๏ธ ๐Ÿ‘ฎ๐Ÿป ๐Ÿ‘ท๐Ÿปโ€โ™€๏ธ ๐Ÿ‘ท๐Ÿป ๐Ÿ’‚๐Ÿปโ€โ™€๏ธ ๐Ÿ’‚๐Ÿป ๐Ÿ•ต๐Ÿปโ€โ™€๏ธ ๐Ÿ•ต๐Ÿป ๐Ÿ‘ฉ๐Ÿปโ€โš•๏ธ ๐Ÿ‘จ๐Ÿปโ€โš•๏ธ ๐Ÿ‘ฉ๐Ÿปโ€๐ŸŒพ ๐Ÿ‘จ๐Ÿปโ€๐ŸŒพ ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿณ ๐Ÿ‘จ๐Ÿปโ€๐Ÿณ ๐Ÿ‘ฉ๐Ÿปโ€๐ŸŽ“ ๐Ÿ‘จ๐Ÿปโ€๐ŸŽ“ ๐Ÿ‘ฉ๐Ÿปโ€๐ŸŽค ๐Ÿ‘จ๐Ÿปโ€๐ŸŽค ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿซ ๐Ÿ‘จ๐Ÿปโ€๐Ÿซ ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿญ ๐Ÿ‘จ๐Ÿปโ€๐Ÿญ ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿ’ป ๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ป ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿ’ผ ๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ผ ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿ”ง ๐Ÿ‘จ๐Ÿปโ€๐Ÿ”ง ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿ”ฌ ๐Ÿ‘จ๐Ÿปโ€๐Ÿ”ฌ ๐Ÿ‘ฉ๐Ÿปโ€๐ŸŽจ ๐Ÿ‘จ๐Ÿปโ€๐ŸŽจ ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿš’ ๐Ÿ‘จ๐Ÿปโ€๐Ÿš’ ๐Ÿ‘ฉ๐Ÿปโ€โœˆ๏ธ ๐Ÿ‘จ๐Ÿปโ€โœˆ๏ธ ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿš€ ๐Ÿ‘จ๐Ÿปโ€๐Ÿš€ ๐Ÿ‘ฉ๐Ÿปโ€โš–๏ธ ๐Ÿ‘จ๐Ÿปโ€โš–๏ธ ๐Ÿคถ๐Ÿป ๐ŸŽ…๐Ÿป ๐Ÿ‘ธ๐Ÿป ๐Ÿคด๐Ÿป ๐Ÿ‘ฐ๐Ÿป ๐Ÿคต๐Ÿป ๐Ÿ‘ผ๐Ÿป ๐Ÿคฐ๐Ÿป ๐Ÿ™‡๐Ÿปโ€โ™€๏ธ ๐Ÿ™‡๐Ÿป ๐Ÿ’๐Ÿป ๐Ÿ’๐Ÿปโ€โ™‚๏ธ ๐Ÿ™…๐Ÿป ๐Ÿ™…๐Ÿปโ€โ™‚๏ธ ๐Ÿ™†๐Ÿป ๐Ÿ™†๐Ÿปโ€โ™‚๏ธ ๐Ÿ™‹๐Ÿป ๐Ÿ™‹๐Ÿปโ€โ™‚๏ธ ๐Ÿคฆ๐Ÿปโ€โ™€๏ธ ๐Ÿคฆ๐Ÿปโ€โ™‚๏ธ ๐Ÿคท๐Ÿปโ€โ™€๏ธ ๐Ÿคท๐Ÿปโ€โ™‚๏ธ ๐Ÿ™Ž๐Ÿป ๐Ÿ™Ž๐Ÿปโ€โ™‚๏ธ ๐Ÿ™๐Ÿป ๐Ÿ™๐Ÿปโ€โ™‚๏ธ ๐Ÿ’‡๐Ÿป ๐Ÿ’‡๐Ÿปโ€โ™‚๏ธ ๐Ÿ’†๐Ÿป ๐Ÿ’†๐Ÿปโ€โ™‚๏ธ ๐Ÿ•ด๐Ÿป ๐Ÿ’ƒ๐Ÿป ๐Ÿ•บ๐Ÿป ๐Ÿšถ๐Ÿปโ€โ™€๏ธ ๐Ÿšถ๐Ÿป ๐Ÿƒ๐Ÿปโ€โ™€๏ธ ๐Ÿƒ๐Ÿป ๐Ÿคฒ๐Ÿป ๐Ÿ‘๐Ÿป ๐Ÿ™Œ๐Ÿป ๐Ÿ‘๐Ÿป ๐Ÿ™๐Ÿป ๐Ÿ‘๐Ÿป ๐Ÿ‘Ž๐Ÿป ๐Ÿ‘Š๐Ÿป โœŠ๐Ÿป ๐Ÿค›๐Ÿป ๐Ÿคœ๐Ÿป ๐Ÿคž๐Ÿป โœŒ๐Ÿป ๐ŸคŸ๐Ÿป ๐Ÿค˜๐Ÿป ๐Ÿ‘Œ๐Ÿป ๐Ÿ‘ˆ๐Ÿป ๐Ÿ‘‰๐Ÿป ๐Ÿ‘†๐Ÿป ๐Ÿ‘‡๐Ÿป โ˜๐Ÿป โœ‹๐Ÿป ๐Ÿคš๐Ÿป ๐Ÿ–๐Ÿป ๐Ÿ––๐Ÿป ๐Ÿ‘‹๐Ÿป ๐Ÿค™๐Ÿป ๐Ÿ’ช๐Ÿป ๐Ÿ–•๐Ÿป โœ๐Ÿป ๐Ÿคณ๐Ÿป ๐Ÿ’…๐Ÿป ๐Ÿ‘‚๐Ÿป ๐Ÿ‘ƒ๐Ÿป๐Ÿ‘ถ๐Ÿผ ๐Ÿ‘ฆ๐Ÿผ ๐Ÿ‘ง๐Ÿผ ๐Ÿ‘จ๐Ÿผ ๐Ÿ‘ฉ๐Ÿผ ๐Ÿ‘ฑ๐Ÿผโ€โ™€๏ธ ๐Ÿ‘ฑ๐Ÿผ ๐Ÿ‘ด๐Ÿผ ๐Ÿ‘ต๐Ÿผ ๐Ÿ‘ฒ๐Ÿผ ๐Ÿ‘ณ๐Ÿผโ€โ™€๏ธ ๐Ÿ‘ณ๐Ÿผ ๐Ÿ‘ฎ๐Ÿผโ€โ™€๏ธ ๐Ÿ‘ฎ๐Ÿผ ๐Ÿ‘ท๐Ÿผโ€โ™€๏ธ ๐Ÿ‘ท๐Ÿผ ๐Ÿ’‚๐Ÿผโ€โ™€๏ธ ๐Ÿ’‚๐Ÿผ ๐Ÿ•ต๐Ÿผโ€โ™€๏ธ ๐Ÿ•ต๐Ÿผ ๐Ÿ‘ฉ๐Ÿผโ€โš•๏ธ ๐Ÿ‘จ๐Ÿผโ€โš•๏ธ ๐Ÿ‘ฉ๐Ÿผโ€๐ŸŒพ ๐Ÿ‘จ๐Ÿผโ€๐ŸŒพ ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿณ ๐Ÿ‘จ๐Ÿผโ€๐Ÿณ ๐Ÿ‘ฉ๐Ÿผโ€๐ŸŽ“ ๐Ÿ‘จ๐Ÿผโ€๐ŸŽ“ ๐Ÿ‘ฉ๐Ÿผโ€๐ŸŽค ๐Ÿ‘จ๐Ÿผโ€๐ŸŽค ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿซ ๐Ÿ‘จ๐Ÿผโ€๐Ÿซ ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿญ ๐Ÿ‘จ๐Ÿผโ€๐Ÿญ ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿ’ป ๐Ÿ‘จ๐Ÿผโ€๐Ÿ’ป ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿ’ผ ๐Ÿ‘จ๐Ÿผโ€๐Ÿ’ผ ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿ”ง ๐Ÿ‘จ๐Ÿผโ€๐Ÿ”ง ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿ”ฌ ๐Ÿ‘จ๐Ÿผโ€๐Ÿ”ฌ ๐Ÿ‘ฉ๐Ÿผโ€๐ŸŽจ ๐Ÿ‘จ๐Ÿผโ€๐ŸŽจ ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿš’ ๐Ÿ‘จ๐Ÿผโ€๐Ÿš’ ๐Ÿ‘ฉ๐Ÿผโ€โœˆ๏ธ ๐Ÿ‘จ๐Ÿผโ€โœˆ๏ธ ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿš€ ๐Ÿ‘จ๐Ÿผโ€๐Ÿš€ ๐Ÿ‘ฉ๐Ÿผโ€โš–๏ธ ๐Ÿ‘จ๐Ÿผโ€โš–๏ธ ๐Ÿคถ๐Ÿผ ๐ŸŽ…๐Ÿผ ๐Ÿ‘ธ๐Ÿผ ๐Ÿคด๐Ÿผ ๐Ÿ‘ฐ๐Ÿผ ๐Ÿคต๐Ÿผ ๐Ÿ‘ผ๐Ÿผ ๐Ÿคฐ๐Ÿผ ๐Ÿ™‡๐Ÿผโ€โ™€๏ธ ๐Ÿ™‡๐Ÿผ ๐Ÿ’๐Ÿผ ๐Ÿ’๐Ÿผโ€โ™‚๏ธ ๐Ÿ™…๐Ÿผ ๐Ÿ™…๐Ÿผโ€โ™‚๏ธ ๐Ÿ™†๐Ÿผ ๐Ÿ™†๐Ÿผโ€โ™‚๏ธ ๐Ÿ™‹๐Ÿผ ๐Ÿ™‹๐Ÿผโ€โ™‚๏ธ ๐Ÿคฆ๐Ÿผโ€โ™€๏ธ ๐Ÿคฆ๐Ÿผโ€โ™‚๏ธ ๐Ÿคท๐Ÿผโ€โ™€๏ธ ๐Ÿคท๐Ÿผโ€โ™‚๏ธ ๐Ÿ™Ž๐Ÿผ ๐Ÿ™Ž๐Ÿผโ€โ™‚๏ธ ๐Ÿ™๐Ÿผ ๐Ÿ™๐Ÿผโ€โ™‚๏ธ ๐Ÿ’‡๐Ÿผ ๐Ÿ’‡๐Ÿผโ€โ™‚๏ธ ๐Ÿ’†๐Ÿผ ๐Ÿ’†๐Ÿผโ€โ™‚๏ธ ๐Ÿ•ด๐Ÿผ ๐Ÿ’ƒ๐Ÿผ ๐Ÿ•บ๐Ÿผ ๐Ÿšถ๐Ÿผโ€โ™€๏ธ ๐Ÿšถ๐Ÿผ ๐Ÿƒ๐Ÿผโ€โ™€๏ธ ๐Ÿƒ๐Ÿผ ๐Ÿคฒ๐Ÿผ ๐Ÿ‘๐Ÿผ ๐Ÿ™Œ๐Ÿผ ๐Ÿ‘๐Ÿผ ๐Ÿ™๐Ÿผ ๐Ÿ‘๐Ÿผ ๐Ÿ‘Ž๐Ÿผ ๐Ÿ‘Š๐Ÿผ โœŠ๐Ÿผ ๐Ÿค›๐Ÿผ ๐Ÿคœ๐Ÿผ ๐Ÿคž๐Ÿผ โœŒ๐Ÿผ ๐ŸคŸ๐Ÿผ ๐Ÿค˜๐Ÿผ ๐Ÿ‘Œ๐Ÿผ ๐Ÿ‘ˆ๐Ÿผ ๐Ÿ‘‰๐Ÿผ ๐Ÿ‘†๐Ÿผ ๐Ÿ‘‡๐Ÿผ โ˜๐Ÿผ โœ‹๐Ÿผ ๐Ÿคš๐Ÿผ ๐Ÿ–๐Ÿผ ๐Ÿ––๐Ÿผ ๐Ÿ‘‹๐Ÿผ ๐Ÿค™๐Ÿผ ๐Ÿ’ช๐Ÿผ ๐Ÿ–•๐Ÿผ โœ๐Ÿผ ๐Ÿคณ๐Ÿผ ๐Ÿ’…๐Ÿผ ๐Ÿ‘‚๐Ÿผ ๐Ÿ‘ƒ๐Ÿผ๐Ÿ‘ถ๐Ÿฝ ๐Ÿ‘ฆ๐Ÿฝ ๐Ÿ‘ง๐Ÿฝ ๐Ÿ‘จ๐Ÿฝ ๐Ÿ‘ฉ๐Ÿฝ ๐Ÿ‘ฑ๐Ÿฝโ€โ™€๏ธ ๐Ÿ‘ฑ๐Ÿฝ ๐Ÿ‘ด๐Ÿฝ ๐Ÿ‘ต๐Ÿฝ ๐Ÿ‘ฒ๐Ÿฝ ๐Ÿ‘ณ๐Ÿฝโ€โ™€๏ธ ๐Ÿ‘ณ๐Ÿฝ ๐Ÿ‘ฎ๐Ÿฝโ€โ™€๏ธ ๐Ÿ‘ฎ๐Ÿฝ ๐Ÿ‘ท๐Ÿฝโ€โ™€๏ธ ๐Ÿ‘ท๐Ÿฝ ๐Ÿ’‚๐Ÿฝโ€โ™€๏ธ ๐Ÿ’‚๐Ÿฝ ๐Ÿ•ต๐Ÿฝโ€โ™€๏ธ ๐Ÿ•ต๐Ÿฝ ๐Ÿ‘ฉ๐Ÿฝโ€โš•๏ธ ๐Ÿ‘จ๐Ÿฝโ€โš•๏ธ ๐Ÿ‘ฉ๐Ÿฝโ€๐ŸŒพ ๐Ÿ‘จ๐Ÿฝโ€๐ŸŒพ ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿณ ๐Ÿ‘จ๐Ÿฝโ€๐Ÿณ ๐Ÿ‘ฉ๐Ÿฝโ€๐ŸŽ“ ๐Ÿ‘จ๐Ÿฝโ€๐ŸŽ“ ๐Ÿ‘ฉ๐Ÿฝโ€๐ŸŽค ๐Ÿ‘จ๐Ÿฝโ€๐ŸŽค ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿซ ๐Ÿ‘จ๐Ÿฝโ€๐Ÿซ ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿญ ๐Ÿ‘จ๐Ÿฝโ€๐Ÿญ ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿ’ป ๐Ÿ‘จ๐Ÿฝโ€๐Ÿ’ป ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿ’ผ ๐Ÿ‘จ๐Ÿฝโ€๐Ÿ’ผ ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿ”ง ๐Ÿ‘จ๐Ÿฝโ€๐Ÿ”ง ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿ”ฌ ๐Ÿ‘จ๐Ÿฝโ€๐Ÿ”ฌ ๐Ÿ‘ฉ๐Ÿฝโ€๐ŸŽจ ๐Ÿ‘จ๐Ÿฝโ€๐ŸŽจ ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿš’ ๐Ÿ‘จ๐Ÿฝโ€๐Ÿš’ ๐Ÿ‘ฉ๐Ÿฝโ€โœˆ๏ธ ๐Ÿ‘จ๐Ÿฝโ€โœˆ๏ธ ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿš€ ๐Ÿ‘จ๐Ÿฝโ€๐Ÿš€ ๐Ÿ‘ฉ๐Ÿฝโ€โš–๏ธ ๐Ÿ‘จ๐Ÿฝโ€โš–๏ธ ๐Ÿคถ๐Ÿฝ ๐ŸŽ…๐Ÿฝ ๐Ÿ‘ธ๐Ÿฝ ๐Ÿคด๐Ÿฝ ๐Ÿ‘ฐ๐Ÿฝ ๐Ÿคต๐Ÿฝ ๐Ÿ‘ผ๐Ÿฝ ๐Ÿคฐ๐Ÿฝ ๐Ÿ™‡๐Ÿฝโ€โ™€๏ธ ๐Ÿ™‡๐Ÿฝ ๐Ÿ’๐Ÿฝ ๐Ÿ’๐Ÿฝโ€โ™‚๏ธ ๐Ÿ™…๐Ÿฝ ๐Ÿ™…๐Ÿฝโ€โ™‚๏ธ ๐Ÿ™†๐Ÿฝ ๐Ÿ™†๐Ÿฝโ€โ™‚๏ธ ๐Ÿ™‹๐Ÿฝ ๐Ÿ™‹๐Ÿฝโ€โ™‚๏ธ ๐Ÿคฆ๐Ÿฝโ€โ™€๏ธ ๐Ÿคฆ๐Ÿฝโ€โ™‚๏ธ ๐Ÿคท๐Ÿฝโ€โ™€๏ธ ๐Ÿคท๐Ÿฝโ€โ™‚๏ธ ๐Ÿ™Ž๐Ÿฝ ๐Ÿ™Ž๐Ÿฝโ€โ™‚๏ธ ๐Ÿ™๐Ÿฝ ๐Ÿ™๐Ÿฝโ€โ™‚๏ธ ๐Ÿ’‡๐Ÿฝ ๐Ÿ’‡๐Ÿฝโ€โ™‚๏ธ ๐Ÿ’†๐Ÿฝ ๐Ÿ’†๐Ÿฝโ€โ™‚๏ธ ๐Ÿ•ด๐Ÿผ ๐Ÿ’ƒ๐Ÿฝ ๐Ÿ•บ๐Ÿฝ ๐Ÿšถ๐Ÿฝโ€โ™€๏ธ ๐Ÿšถ๐Ÿฝ ๐Ÿƒ๐Ÿฝโ€โ™€๏ธ ๐Ÿƒ๐Ÿฝ ๐Ÿคฒ๐Ÿฝ ๐Ÿ‘๐Ÿฝ ๐Ÿ™Œ๐Ÿฝ ๐Ÿ‘๐Ÿฝ ๐Ÿ™๐Ÿฝ ๐Ÿ‘๐Ÿฝ ๐Ÿ‘Ž๐Ÿฝ ๐Ÿ‘Š๐Ÿฝ โœŠ๐Ÿฝ ๐Ÿค›๐Ÿฝ ๐Ÿคœ๐Ÿฝ ๐Ÿคž๐Ÿฝ โœŒ๐Ÿฝ ๐ŸคŸ๐Ÿฝ ๐Ÿค˜๐Ÿฝ ๐Ÿ‘Œ๐Ÿฝ ๐Ÿ‘ˆ๐Ÿฝ ๐Ÿ‘‰๐Ÿฝ ๐Ÿ‘†๐Ÿฝ ๐Ÿ‘‡๐Ÿฝ โ˜๐Ÿฝ โœ‹๐Ÿฝ ๐Ÿคš๐Ÿฝ ๐Ÿ–๐Ÿฝ ๐Ÿ––๐Ÿฝ ๐Ÿ‘‹๐Ÿฝ ๐Ÿค™๐Ÿฝ ๐Ÿ’ช๐Ÿฝ ๐Ÿ–•๐Ÿฝ โœ๐Ÿฝ ๐Ÿคณ๐Ÿฝ ๐Ÿ’…๐Ÿฝ ๐Ÿ‘‚๐Ÿฝ ๐Ÿ‘ƒ๐Ÿฝ๐Ÿ‘ถ๐Ÿพ ๐Ÿ‘ฆ๐Ÿพ ๐Ÿ‘ง๐Ÿพ ๐Ÿ‘จ๐Ÿพ ๐Ÿ‘ฉ๐Ÿพ ๐Ÿ‘ฑ๐Ÿพโ€โ™€๏ธ ๐Ÿ‘ฑ๐Ÿพ ๐Ÿ‘ด๐Ÿพ ๐Ÿ‘ต๐Ÿพ ๐Ÿ‘ฒ๐Ÿพ ๐Ÿ‘ณ๐Ÿพโ€โ™€๏ธ ๐Ÿ‘ณ๐Ÿพ ๐Ÿ‘ฎ๐Ÿพโ€โ™€๏ธ ๐Ÿ‘ฎ๐Ÿพ ๐Ÿ‘ท๐Ÿพโ€โ™€๏ธ ๐Ÿ‘ท๐Ÿพ ๐Ÿ’‚๐Ÿพโ€โ™€๏ธ ๐Ÿ’‚๐Ÿพ ๐Ÿ•ต๐Ÿพโ€โ™€๏ธ ๐Ÿ•ต๐Ÿพ ๐Ÿ‘ฉ๐Ÿพโ€โš•๏ธ ๐Ÿ‘จ๐Ÿพโ€โš•๏ธ ๐Ÿ‘ฉ๐Ÿพโ€๐ŸŒพ ๐Ÿ‘จ๐Ÿพโ€๐ŸŒพ ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿณ ๐Ÿ‘จ๐Ÿพโ€๐Ÿณ ๐Ÿ‘ฉ๐Ÿพโ€๐ŸŽ“ ๐Ÿ‘จ๐Ÿพโ€๐ŸŽ“ ๐Ÿ‘ฉ๐Ÿพโ€๐ŸŽค ๐Ÿ‘จ๐Ÿพโ€๐ŸŽค ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿซ ๐Ÿ‘จ๐Ÿพโ€๐Ÿซ ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿญ ๐Ÿ‘จ๐Ÿพโ€๐Ÿญ ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿ’ป ๐Ÿ‘จ๐Ÿพโ€๐Ÿ’ป ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿ’ผ ๐Ÿ‘จ๐Ÿพโ€๐Ÿ’ผ ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿ”ง ๐Ÿ‘จ๐Ÿพโ€๐Ÿ”ง ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿ”ฌ ๐Ÿ‘จ๐Ÿพโ€๐Ÿ”ฌ ๐Ÿ‘ฉ๐Ÿพโ€๐ŸŽจ ๐Ÿ‘จ๐Ÿพโ€๐ŸŽจ ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿš’ ๐Ÿ‘จ๐Ÿพโ€๐Ÿš’ ๐Ÿ‘ฉ๐Ÿพโ€โœˆ๏ธ ๐Ÿ‘จ๐Ÿพโ€โœˆ๏ธ ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿš€ ๐Ÿ‘จ๐Ÿพโ€๐Ÿš€ ๐Ÿ‘ฉ๐Ÿพโ€โš–๏ธ ๐Ÿ‘จ๐Ÿพโ€โš–๏ธ ๐Ÿคถ๐Ÿพ ๐ŸŽ…๐Ÿพ ๐Ÿ‘ธ๐Ÿพ ๐Ÿคด๐Ÿพ ๐Ÿ‘ฐ๐Ÿพ ๐Ÿคต๐Ÿพ ๐Ÿ‘ผ๐Ÿพ ๐Ÿคฐ๐Ÿพ ๐Ÿ™‡๐Ÿพโ€โ™€๏ธ ๐Ÿ™‡๐Ÿพ ๐Ÿ’๐Ÿพ ๐Ÿ’๐Ÿพโ€โ™‚๏ธ ๐Ÿ™…๐Ÿพ ๐Ÿ™…๐Ÿพโ€โ™‚๏ธ ๐Ÿ™†๐Ÿพ ๐Ÿ™†๐Ÿพโ€โ™‚๏ธ ๐Ÿ™‹๐Ÿพ ๐Ÿ™‹๐Ÿพโ€โ™‚๏ธ ๐Ÿคฆ๐Ÿพโ€โ™€๏ธ ๐Ÿคฆ๐Ÿพโ€โ™‚๏ธ ๐Ÿคท๐Ÿพโ€โ™€๏ธ ๐Ÿคท๐Ÿพโ€โ™‚๏ธ ๐Ÿ™Ž๐Ÿพ ๐Ÿ™Ž๐Ÿพโ€โ™‚๏ธ ๐Ÿ™๐Ÿพ ๐Ÿ™๐Ÿพโ€โ™‚๏ธ ๐Ÿ’‡๐Ÿพ ๐Ÿ’‡๐Ÿพโ€โ™‚๏ธ ๐Ÿ’†๐Ÿพ ๐Ÿ’†๐Ÿพโ€โ™‚๏ธ ๐Ÿ•ด๐Ÿพ ๐Ÿ’ƒ๐Ÿพ ๐Ÿ•บ๐Ÿพ ๐Ÿšถ๐Ÿพโ€โ™€๏ธ ๐Ÿšถ๐Ÿพ ๐Ÿƒ๐Ÿพโ€โ™€๏ธ ๐Ÿƒ๐Ÿพ ๐Ÿคฒ๐Ÿพ ๐Ÿ‘๐Ÿพ ๐Ÿ™Œ๐Ÿพ ๐Ÿ‘๐Ÿพ ๐Ÿ™๐Ÿพ ๐Ÿ‘๐Ÿพ ๐Ÿ‘Ž๐Ÿพ ๐Ÿ‘Š๐Ÿพ โœŠ๐Ÿพ ๐Ÿค›๐Ÿพ ๐Ÿคœ๐Ÿพ ๐Ÿคž๐Ÿพ โœŒ๐Ÿพ ๐ŸคŸ๐Ÿพ ๐Ÿค˜๐Ÿพ ๐Ÿ‘Œ๐Ÿพ ๐Ÿ‘ˆ๐Ÿพ ๐Ÿ‘‰๐Ÿพ ๐Ÿ‘†๐Ÿพ ๐Ÿ‘‡๐Ÿพ โ˜๐Ÿพ โœ‹๐Ÿพ ๐Ÿคš๐Ÿพ ๐Ÿ–๐Ÿพ ๐Ÿ––๐Ÿพ ๐Ÿ‘‹๐Ÿพ ๐Ÿค™๐Ÿพ ๐Ÿ’ช๐Ÿพ ๐Ÿ–•๐Ÿพ โœ๐Ÿพ ๐Ÿคณ๐Ÿพ ๐Ÿ’…๐Ÿพ ๐Ÿ‘‚๐Ÿพ ๐Ÿ‘ƒ๐Ÿพ๐Ÿ‘ถ๐Ÿฟ ๐Ÿ‘ฆ๐Ÿฟ ๐Ÿ‘ง๐Ÿฟ ๐Ÿ‘จ๐Ÿฟ ๐Ÿ‘ฉ๐Ÿฟ ๐Ÿ‘ฑ๐Ÿฟโ€โ™€๏ธ ๐Ÿ‘ฑ๐Ÿฟ ๐Ÿ‘ด๐Ÿฟ ๐Ÿ‘ต๐Ÿฟ ๐Ÿ‘ฒ๐Ÿฟ ๐Ÿ‘ณ๐Ÿฟโ€โ™€๏ธ ๐Ÿ‘ณ๐Ÿฟ ๐Ÿ‘ฎ๐Ÿฟโ€โ™€๏ธ ๐Ÿ‘ฎ๐Ÿฟ ๐Ÿ‘ท๐Ÿฟโ€โ™€๏ธ ๐Ÿ‘ท๐Ÿฟ ๐Ÿ’‚๐Ÿฟโ€โ™€๏ธ ๐Ÿ’‚๐Ÿฟ ๐Ÿ•ต๐Ÿฟโ€โ™€๏ธ ๐Ÿ•ต๐Ÿฟ ๐Ÿ‘ฉ๐Ÿฟโ€โš•๏ธ ๐Ÿ‘จ๐Ÿฟโ€โš•๏ธ ๐Ÿ‘ฉ๐Ÿฟโ€๐ŸŒพ ๐Ÿ‘จ๐Ÿฟโ€๐ŸŒพ ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿณ ๐Ÿ‘จ๐Ÿฟโ€๐Ÿณ ๐Ÿ‘ฉ๐Ÿฟโ€๐ŸŽ“ ๐Ÿ‘จ๐Ÿฟโ€๐ŸŽ“ ๐Ÿ‘ฉ๐Ÿฟโ€๐ŸŽค ๐Ÿ‘จ๐Ÿฟโ€๐ŸŽค ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿซ ๐Ÿ‘จ๐Ÿฟโ€๐Ÿซ ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿญ ๐Ÿ‘จ๐Ÿฟโ€๐Ÿญ ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿ’ป ๐Ÿ‘จ๐Ÿฟโ€๐Ÿ’ป ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿ’ผ ๐Ÿ‘จ๐Ÿฟโ€๐Ÿ’ผ ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿ”ง ๐Ÿ‘จ๐Ÿฟโ€๐Ÿ”ง ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿ”ฌ ๐Ÿ‘จ๐Ÿฟโ€๐Ÿ”ฌ ๐Ÿ‘ฉ๐Ÿฟโ€๐ŸŽจ ๐Ÿ‘จ๐Ÿฟโ€๐ŸŽจ ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿš’ ๐Ÿ‘จ๐Ÿฟโ€๐Ÿš’ ๐Ÿ‘ฉ๐Ÿฟโ€โœˆ๏ธ ๐Ÿ‘จ๐Ÿฟโ€โœˆ๏ธ ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿš€ ๐Ÿ‘จ๐Ÿฟโ€๐Ÿš€ ๐Ÿ‘ฉ๐Ÿฟโ€โš–๏ธ ๐Ÿ‘จ๐Ÿฟโ€โš–๏ธ ๐Ÿคถ๐Ÿฟ ๐ŸŽ…๐Ÿฟ ๐Ÿ‘ธ๐Ÿฟ ๐Ÿคด๐Ÿฟ ๐Ÿ‘ฐ๐Ÿฟ ๐Ÿคต๐Ÿฟ ๐Ÿ‘ผ๐Ÿฟ ๐Ÿคฐ๐Ÿฟ ๐Ÿ™‡๐Ÿฟโ€โ™€๏ธ ๐Ÿ™‡๐Ÿฟ ๐Ÿ’๐Ÿฟ ๐Ÿ’๐Ÿฟโ€โ™‚๏ธ ๐Ÿ™…๐Ÿฟ ๐Ÿ™…๐Ÿฟโ€โ™‚๏ธ ๐Ÿ™†๐Ÿฟ ๐Ÿ™†๐Ÿฟโ€โ™‚๏ธ ๐Ÿ™‹๐Ÿฟ ๐Ÿ™‹๐Ÿฟโ€โ™‚๏ธ ๐Ÿคฆ๐Ÿฟโ€โ™€๏ธ ๐Ÿคฆ๐Ÿฟโ€โ™‚๏ธ ๐Ÿคท๐Ÿฟโ€โ™€๏ธ ๐Ÿคท๐Ÿฟโ€โ™‚๏ธ ๐Ÿ™Ž๐Ÿฟ ๐Ÿ™Ž๐Ÿฟโ€โ™‚๏ธ ๐Ÿ™๐Ÿฟ ๐Ÿ™๐Ÿฟโ€โ™‚๏ธ ๐Ÿ’‡๐Ÿฟ ๐Ÿ’‡๐Ÿฟโ€โ™‚๏ธ ๐Ÿ’†๐Ÿฟ ๐Ÿ’†๐Ÿฟโ€โ™‚๏ธ ๐Ÿ•ด๐Ÿฟ ๐Ÿ’ƒ๐Ÿฟ ๐Ÿ•บ๐Ÿฟ ๐Ÿšถ๐Ÿฟโ€โ™€๏ธ ๐Ÿšถ๐Ÿฟ ๐Ÿƒ๐Ÿฟโ€โ™€๏ธ ๐Ÿƒ๐Ÿฟ ๐Ÿคฒ๐Ÿฟ ๐Ÿ‘๐Ÿฟ ๐Ÿ™Œ๐Ÿฟ ๐Ÿ‘๐Ÿฟ ๐Ÿ™๐Ÿฟ ๐Ÿ‘๐Ÿฟ ๐Ÿ‘Ž๐Ÿฟ ๐Ÿ‘Š๐Ÿฟ โœŠ๐Ÿฟ ๐Ÿค›๐Ÿฟ ๐Ÿคœ๐Ÿฟ ๐Ÿคž๐Ÿฟ โœŒ๐Ÿฟ ๐ŸคŸ๐Ÿฟ ๐Ÿค˜๐Ÿฟ ๐Ÿ‘Œ๐Ÿฟ ๐Ÿ‘ˆ๐Ÿฟ ๐Ÿ‘‰๐Ÿฟ ๐Ÿ‘†๐Ÿฟ ๐Ÿ‘‡๐Ÿฟ โ˜๐Ÿฟ โœ‹๐Ÿฟ ๐Ÿคš๐Ÿฟ ๐Ÿ–๐Ÿฟ ๐Ÿ––๐Ÿฟ ๐Ÿ‘‹๐Ÿฟ ๐Ÿค™๐Ÿฟ ๐Ÿ’ช๐Ÿฟ ๐Ÿ–•๐Ÿฟ โœ๐Ÿฟ ๐Ÿคณ๐Ÿฟ ๐Ÿ’…๐Ÿฟ ๐Ÿ‘‚๐Ÿฟ ๐Ÿ‘ƒ๐Ÿฟ๐Ÿถ ๐Ÿฑ ๐Ÿญ ๐Ÿน ๐Ÿฐ ๐ŸฆŠ ๐Ÿฆ ๐Ÿป ๐Ÿผ ๐Ÿฆ˜ ๐Ÿฆก ๐Ÿจ ๐Ÿฏ ๐Ÿฆ ๐Ÿฎ ๐Ÿท ๐Ÿฝ ๐Ÿธ ๐Ÿต ๐Ÿ™ˆ ๐Ÿ™‰ ๐Ÿ™Š ๐Ÿ’ ๐Ÿ” ๐Ÿง ๐Ÿฆ ๐Ÿค ๐Ÿฃ ๐Ÿฅ ๐Ÿฆ† ๐Ÿฆข ๐Ÿฆ… ๐Ÿฆ‰ ๐Ÿฆš ๐Ÿฆœ ๐Ÿฆ‡ ๐Ÿบ ๐Ÿ— ๐Ÿด ๐Ÿฆ„ ๐Ÿ ๐Ÿ› ๐Ÿฆ‹ ๐ŸŒ ๐Ÿš ๐Ÿž ๐Ÿœ ๐Ÿฆ— ๐Ÿ•ท ๐Ÿ•ธ ๐Ÿฆ‚ ๐ŸฆŸ ๐Ÿฆ  ๐Ÿข ๐Ÿ ๐ŸฆŽ ๐Ÿฆ– ๐Ÿฆ• ๐Ÿ™ ๐Ÿฆ‘ ๐Ÿฆ ๐Ÿฆ€ ๐Ÿก ๐Ÿ  ๐ŸŸ ๐Ÿฌ ๐Ÿณ ๐Ÿ‹ ๐Ÿฆˆ ๐ŸŠ ๐Ÿ… ๐Ÿ† ๐Ÿฆ“ ๐Ÿฆ ๐Ÿ˜ ๐Ÿฆ ๐Ÿฆ› ๐Ÿช ๐Ÿซ ๐Ÿฆ™ ๐Ÿฆ’ ๐Ÿƒ ๐Ÿ‚ ๐Ÿ„ ๐ŸŽ ๐Ÿ– ๐Ÿ ๐Ÿ‘ ๐Ÿ ๐ŸฆŒ ๐Ÿ• ๐Ÿฉ ๐Ÿˆ ๐Ÿ“ ๐Ÿฆƒ ๐Ÿ•Š ๐Ÿ‡ ๐Ÿ ๐Ÿ€ ๐Ÿฟ ๐Ÿฆ” ๐Ÿพ ๐Ÿ‰ ๐Ÿฒ ๐ŸŒต ๐ŸŽ„ ๐ŸŒฒ ๐ŸŒณ ๐ŸŒด ๐ŸŒฑ ๐ŸŒฟ โ˜˜๏ธ ๐Ÿ€ ๐ŸŽ ๐ŸŽ‹ ๐Ÿƒ ๐Ÿ‚ ๐Ÿ ๐Ÿ„ ๐ŸŒพ ๐Ÿ’ ๐ŸŒท ๐ŸŒน ๐Ÿฅ€ ๐ŸŒบ ๐ŸŒธ ๐ŸŒผ ๐ŸŒป ๐ŸŒž ๐ŸŒ ๐ŸŒ› ๐ŸŒœ ๐ŸŒš ๐ŸŒ• ๐ŸŒ– ๐ŸŒ— ๐ŸŒ˜ ๐ŸŒ‘ ๐ŸŒ’ ๐ŸŒ“ ๐ŸŒ” ๐ŸŒ™ ๐ŸŒŽ ๐ŸŒ ๐ŸŒ ๐Ÿ’ซ โญ๏ธ ๐ŸŒŸ โœจ โšก๏ธ โ˜„๏ธ ๐Ÿ’ฅ ๐Ÿ”ฅ ๐ŸŒช ๐ŸŒˆ โ˜€๏ธ ๐ŸŒค โ›…๏ธ ๐ŸŒฅ โ˜๏ธ ๐ŸŒฆ ๐ŸŒง โ›ˆ ๐ŸŒฉ ๐ŸŒจ โ„๏ธ โ˜ƒ๏ธ โ›„๏ธ ๐ŸŒฌ ๐Ÿ’จ ๐Ÿ’ง ๐Ÿ’ฆ โ˜”๏ธ โ˜‚๏ธ ๐ŸŒŠ ๐ŸŒซ๐Ÿ ๐ŸŽ ๐Ÿ ๐ŸŠ ๐Ÿ‹ ๐ŸŒ ๐Ÿ‰ ๐Ÿ‡ ๐Ÿ“ ๐Ÿˆ ๐Ÿ’ ๐Ÿ‘ ๐Ÿ ๐Ÿฅญ ๐Ÿฅฅ ๐Ÿฅ ๐Ÿ… ๐Ÿ† ๐Ÿฅ‘ ๐Ÿฅฆ ๐Ÿฅ’ ๐Ÿฅฌ ๐ŸŒถ ๐ŸŒฝ ๐Ÿฅ• ๐Ÿฅ” ๐Ÿ  ๐Ÿฅ ๐Ÿž ๐Ÿฅ– ๐Ÿฅจ ๐Ÿฅฏ ๐Ÿง€ ๐Ÿฅš ๐Ÿณ ๐Ÿฅž ๐Ÿฅ“ ๐Ÿฅฉ ๐Ÿ— ๐Ÿ– ๐ŸŒญ ๐Ÿ” ๐ŸŸ ๐Ÿ• ๐Ÿฅช ๐Ÿฅ™ ๐ŸŒฎ ๐ŸŒฏ ๐Ÿฅ— ๐Ÿฅ˜ ๐Ÿฅซ ๐Ÿ ๐Ÿœ ๐Ÿฒ ๐Ÿ› ๐Ÿฃ ๐Ÿฑ ๐ŸฅŸ ๐Ÿค ๐Ÿ™ ๐Ÿš ๐Ÿ˜ ๐Ÿฅ ๐Ÿฅฎ ๐Ÿฅ  ๐Ÿข ๐Ÿก ๐Ÿง ๐Ÿจ ๐Ÿฆ ๐Ÿฅง ๐Ÿฐ ๐ŸŽ‚ ๐Ÿฎ ๐Ÿญ ๐Ÿฌ ๐Ÿซ ๐Ÿฟ ๐Ÿง‚ ๐Ÿฉ ๐Ÿช ๐ŸŒฐ ๐Ÿฅœ ๐Ÿฏ ๐Ÿฅ› ๐Ÿผ โ˜•๏ธ ๐Ÿต ๐Ÿฅค ๐Ÿถ ๐Ÿบ ๐Ÿป ๐Ÿฅ‚ ๐Ÿท ๐Ÿฅƒ ๐Ÿธ ๐Ÿน ๐Ÿพ ๐Ÿฅ„ ๐Ÿด ๐Ÿฝ ๐Ÿฅฃ ๐Ÿฅก ๐Ÿฅขโšฝ๏ธ ๐Ÿ€ ๐Ÿˆ โšพ๏ธ ๐ŸฅŽ ๐Ÿ ๐Ÿ‰ ๐ŸŽพ ๐Ÿฅ ๐ŸŽฑ ๐Ÿ“ ๐Ÿธ ๐Ÿฅ… ๐Ÿ’ ๐Ÿ‘ ๐Ÿฅ ๐Ÿ โ›ณ๏ธ ๐Ÿน ๐ŸŽฃ ๐ŸฅŠ ๐Ÿฅ‹ ๐ŸŽฝ โ›ธ ๐ŸฅŒ ๐Ÿ›ท ๐Ÿ›น ๐ŸŽฟ โ›ท ๐Ÿ‚ ๐Ÿ‹๏ธโ€โ™€๏ธ ๐Ÿ‹๐Ÿปโ€โ™€๏ธ ๐Ÿ‹๐Ÿผโ€โ™€๏ธ ๐Ÿ‹๐Ÿฝโ€โ™€๏ธ ๐Ÿ‹๐Ÿพโ€โ™€๏ธ ๐Ÿ‹๐Ÿฟโ€โ™€๏ธ ๐Ÿ‹๏ธโ€โ™‚๏ธ ๐Ÿ‹๐Ÿปโ€โ™‚๏ธ ๐Ÿ‹๐Ÿผโ€โ™‚๏ธ ๐Ÿ‹๐Ÿฝโ€โ™‚๏ธ ๐Ÿ‹๐Ÿพโ€โ™‚๏ธ ๐Ÿ‹๐Ÿฟโ€โ™‚๏ธ ๐Ÿคผโ€โ™€๏ธ ๐Ÿคผโ€โ™‚๏ธ ๐Ÿคธโ€โ™€๏ธ ๐Ÿคธ๐Ÿปโ€โ™€๏ธ ๐Ÿคธ๐Ÿผโ€โ™€๏ธ ๐Ÿคธ๐Ÿฝโ€โ™€๏ธ ๐Ÿคธ๐Ÿพโ€โ™€๏ธ ๐Ÿคธ๐Ÿฟโ€โ™€๏ธ ๐Ÿคธโ€โ™‚๏ธ ๐Ÿคธ๐Ÿปโ€โ™‚๏ธ ๐Ÿคธ๐Ÿผโ€โ™‚๏ธ ๐Ÿคธ๐Ÿฝโ€โ™‚๏ธ ๐Ÿคธ๐Ÿพโ€โ™‚๏ธ ๐Ÿคธ๐Ÿฟโ€โ™‚๏ธ โ›น๏ธโ€โ™€๏ธ โ›น๐Ÿปโ€โ™€๏ธ โ›น๐Ÿผโ€โ™€๏ธ โ›น๐Ÿฝโ€โ™€๏ธ โ›น๐Ÿพโ€โ™€๏ธ โ›น๐Ÿฟโ€โ™€๏ธ โ›น๏ธโ€โ™‚๏ธ โ›น๐Ÿปโ€โ™‚๏ธ โ›น๐Ÿผโ€โ™‚๏ธ โ›น๐Ÿฝโ€โ™‚๏ธ โ›น๐Ÿพโ€โ™‚๏ธ โ›น๐Ÿฟโ€โ™‚๏ธ ๐Ÿคบ ๐Ÿคพโ€โ™€๏ธ ๐Ÿคพ๐Ÿปโ€โ™€๏ธ ๐Ÿคพ๐Ÿผโ€โ™€๏ธ ๐Ÿคพ๐Ÿพโ€โ™€๏ธ ๐Ÿคพ๐Ÿพโ€โ™€๏ธ ๐Ÿคพ๐Ÿฟโ€โ™€๏ธ ๐Ÿคพโ€โ™‚๏ธ ๐Ÿคพ๐Ÿปโ€โ™‚๏ธ ๐Ÿคพ๐Ÿผโ€โ™‚๏ธ ๐Ÿคพ๐Ÿฝโ€โ™‚๏ธ ๐Ÿคพ๐Ÿพโ€โ™‚๏ธ ๐Ÿคพ๐Ÿฟโ€โ™‚๏ธ ๐ŸŒ๏ธโ€โ™€๏ธ ๐ŸŒ๐Ÿปโ€โ™€๏ธ ๐ŸŒ๐Ÿผโ€โ™€๏ธ ๐ŸŒ๐Ÿฝโ€โ™€๏ธ ๐ŸŒ๐Ÿพโ€โ™€๏ธ ๐ŸŒ๐Ÿฟโ€โ™€๏ธ ๐ŸŒ๏ธโ€โ™‚๏ธ ๐ŸŒ๐Ÿปโ€โ™‚๏ธ ๐ŸŒ๐Ÿผโ€โ™‚๏ธ ๐ŸŒ๐Ÿฝโ€โ™‚๏ธ ๐ŸŒ๐Ÿพโ€โ™‚๏ธ ๐ŸŒ๐Ÿฟโ€โ™‚๏ธ ๐Ÿ‡ ๐Ÿ‡๐Ÿป ๐Ÿ‡๐Ÿผ ๐Ÿ‡๐Ÿฝ ๐Ÿ‡๐Ÿพ ๐Ÿ‡๐Ÿฟ ๐Ÿง˜โ€โ™€๏ธ ๐Ÿง˜๐Ÿปโ€โ™€๏ธ ๐Ÿง˜๐Ÿผโ€โ™€๏ธ ๐Ÿง˜๐Ÿฝโ€โ™€๏ธ ๐Ÿง˜๐Ÿพโ€โ™€๏ธ ๐Ÿง˜๐Ÿฟโ€โ™€๏ธ ๐Ÿง˜โ€โ™‚๏ธ ๐Ÿง˜๐Ÿปโ€โ™‚๏ธ ๐Ÿง˜๐Ÿผโ€โ™‚๏ธ ๐Ÿง˜๐Ÿฝโ€โ™‚๏ธ ๐Ÿง˜๐Ÿพโ€โ™‚๏ธ ๐Ÿง˜๐Ÿฟโ€โ™‚๏ธ ๐Ÿ„โ€โ™€๏ธ ๐Ÿ„๐Ÿปโ€โ™€๏ธ ๐Ÿ„๐Ÿผโ€โ™€๏ธ ๐Ÿ„๐Ÿฝโ€โ™€๏ธ ๐Ÿ„๐Ÿพโ€โ™€๏ธ ๐Ÿ„๐Ÿฟโ€โ™€๏ธ ๐Ÿ„โ€โ™‚๏ธ ๐Ÿ„๐Ÿปโ€โ™‚๏ธ ๐Ÿ„๐Ÿผโ€โ™‚๏ธ ๐Ÿ„๐Ÿฝโ€โ™‚๏ธ ๐Ÿ„๐Ÿพโ€โ™‚๏ธ ๐Ÿ„๐Ÿฟโ€โ™‚๏ธ ๐ŸŠโ€โ™€๏ธ ๐ŸŠ๐Ÿปโ€โ™€๏ธ ๐ŸŠ๐Ÿผโ€โ™€๏ธ ๐ŸŠ๐Ÿฝโ€โ™€๏ธ ๐ŸŠ๐Ÿพโ€โ™€๏ธ ๐ŸŠ๐Ÿฟโ€โ™€๏ธ ๐ŸŠโ€โ™‚๏ธ ๐ŸŠ๐Ÿปโ€โ™‚๏ธ ๐ŸŠ๐Ÿผโ€โ™‚๏ธ ๐ŸŠ๐Ÿฝโ€โ™‚๏ธ ๐ŸŠ๐Ÿพโ€โ™‚๏ธ ๐ŸŠ๐Ÿฟโ€โ™‚๏ธ ๐Ÿคฝโ€โ™€๏ธ ๐Ÿคฝ๐Ÿปโ€โ™€๏ธ ๐Ÿคฝ๐Ÿผโ€โ™€๏ธ ๐Ÿคฝ๐Ÿฝโ€โ™€๏ธ ๐Ÿคฝ๐Ÿพโ€โ™€๏ธ ๐Ÿคฝ๐Ÿฟโ€โ™€๏ธ ๐Ÿคฝโ€โ™‚๏ธ ๐Ÿคฝ๐Ÿปโ€โ™‚๏ธ ๐Ÿคฝ๐Ÿผโ€โ™‚๏ธ ๐Ÿคฝ๐Ÿฝโ€โ™‚๏ธ ๐Ÿคฝ๐Ÿพโ€โ™‚๏ธ ๐Ÿคฝ๐Ÿฟโ€โ™‚๏ธ ๐Ÿšฃโ€โ™€๏ธ ๐Ÿšฃ๐Ÿปโ€โ™€๏ธ ๐Ÿšฃ๐Ÿผโ€โ™€๏ธ ๐Ÿšฃ๐Ÿฝโ€โ™€๏ธ ๐Ÿšฃ๐Ÿพโ€โ™€๏ธ ๐Ÿšฃ๐Ÿฟโ€โ™€๏ธ ๐Ÿšฃโ€โ™‚๏ธ ๐Ÿšฃ๐Ÿปโ€โ™‚๏ธ ๐Ÿšฃ๐Ÿผโ€โ™‚๏ธ ๐Ÿšฃ๐Ÿฝโ€โ™‚๏ธ ๐Ÿšฃ๐Ÿพโ€โ™‚๏ธ ๐Ÿšฃ๐Ÿฟโ€โ™‚๏ธ ๐Ÿง—โ€โ™€๏ธ ๐Ÿง—๐Ÿปโ€โ™€๏ธ ๐Ÿง—๐Ÿผโ€โ™€๏ธ ๐Ÿง—๐Ÿฝโ€โ™€๏ธ ๐Ÿง—๐Ÿพโ€โ™€๏ธ ๐Ÿง—๐Ÿฟโ€โ™€๏ธ ๐Ÿง—โ€โ™‚๏ธ ๐Ÿง—๐Ÿปโ€โ™‚๏ธ ๐Ÿง—๐Ÿผโ€โ™‚๏ธ ๐Ÿง—๐Ÿฝโ€โ™‚๏ธ ๐Ÿง—๐Ÿพโ€โ™‚๏ธ ๐Ÿง—๐Ÿฟโ€โ™‚๏ธ ๐Ÿšตโ€โ™€๏ธ ๐Ÿšต๐Ÿปโ€โ™€๏ธ ๐Ÿšต๐Ÿผโ€โ™€๏ธ ๐Ÿšต๐Ÿฝโ€โ™€๏ธ ๐Ÿšต๐Ÿพโ€โ™€๏ธ ๐Ÿšต๐Ÿฟโ€โ™€๏ธ ๐Ÿšตโ€โ™‚๏ธ ๐Ÿšต๐Ÿปโ€โ™‚๏ธ ๐Ÿšต๐Ÿผโ€โ™‚๏ธ ๐Ÿšต๐Ÿฝโ€โ™‚๏ธ ๐Ÿšต๐Ÿพโ€โ™‚๏ธ ๐Ÿšต๐Ÿฟโ€โ™‚๏ธ ๐Ÿšดโ€โ™€๏ธ ๐Ÿšด๐Ÿปโ€โ™€๏ธ ๐Ÿšด๐Ÿผโ€โ™€๏ธ ๐Ÿšด๐Ÿฝโ€โ™€๏ธ ๐Ÿšด๐Ÿพโ€โ™€๏ธ ๐Ÿšด๐Ÿฟโ€โ™€๏ธ ๐Ÿšดโ€โ™‚๏ธ ๐Ÿšด๐Ÿปโ€โ™‚๏ธ ๐Ÿšด๐Ÿผโ€โ™‚๏ธ ๐Ÿšด๐Ÿฝโ€โ™‚๏ธ ๐Ÿšด๐Ÿพโ€โ™‚๏ธ ๐Ÿšด๐Ÿฟโ€โ™‚๏ธ ๐Ÿ† ๐Ÿฅ‡ ๐Ÿฅˆ ๐Ÿฅ‰ ๐Ÿ… ๐ŸŽ– ๐Ÿต ๐ŸŽ— ๐ŸŽซ ๐ŸŽŸ ๐ŸŽช ๐Ÿคนโ€โ™€๏ธ ๐Ÿคน๐Ÿปโ€โ™€๏ธ ๐Ÿคน๐Ÿผโ€โ™€๏ธ ๐Ÿคน๐Ÿฝโ€โ™€๏ธ ๐Ÿคน๐Ÿพโ€โ™€๏ธ ๐Ÿคน๐Ÿฟโ€โ™€๏ธ ๐Ÿคนโ€โ™‚๏ธ ๐Ÿคน๐Ÿปโ€โ™‚๏ธ ๐Ÿคน๐Ÿผโ€โ™‚๏ธ ๐Ÿคน๐Ÿฝโ€โ™‚๏ธ ๐Ÿคน๐Ÿพโ€โ™‚๏ธ ๐Ÿคน๐Ÿฟโ€โ™‚๏ธ ๐ŸŽญ ๐ŸŽจ ๐ŸŽฌ ๐ŸŽค ๐ŸŽง ๐ŸŽผ ๐ŸŽน ๐Ÿฅ ๐ŸŽท ๐ŸŽบ ๐ŸŽธ ๐ŸŽป ๐ŸŽฒ ๐Ÿงฉ โ™Ÿ ๐ŸŽฏ ๐ŸŽณ ๐ŸŽฎ ๐ŸŽฐ๐Ÿš— ๐Ÿš• ๐Ÿš™ ๐ŸšŒ ๐ŸšŽ ๐ŸŽ ๐Ÿš“ ๐Ÿš‘ ๐Ÿš’ ๐Ÿš ๐Ÿšš ๐Ÿš› ๐Ÿšœ ๐Ÿ›ด ๐Ÿšฒ ๐Ÿ›ต ๐Ÿ ๐Ÿšจ ๐Ÿš” ๐Ÿš ๐Ÿš˜ ๐Ÿš– ๐Ÿšก ๐Ÿš  ๐ŸšŸ ๐Ÿšƒ ๐Ÿš‹ ๐Ÿšž ๐Ÿš ๐Ÿš„ ๐Ÿš… ๐Ÿšˆ ๐Ÿš‚ ๐Ÿš† ๐Ÿš‡ ๐ŸšŠ ๐Ÿš‰ โœˆ๏ธ ๐Ÿ›ซ ๐Ÿ›ฌ ๐Ÿ›ฉ ๐Ÿ’บ ๐Ÿ›ฐ ๐Ÿš€ ๐Ÿ›ธ ๐Ÿš ๐Ÿ›ถ โ›ต๏ธ ๐Ÿšค ๐Ÿ›ฅ ๐Ÿ›ณ โ›ด ๐Ÿšข โš“๏ธ โ›ฝ๏ธ ๐Ÿšง ๐Ÿšฆ ๐Ÿšฅ ๐Ÿš ๐Ÿ—บ ๐Ÿ—ฟ ๐Ÿ—ฝ ๐Ÿ—ผ ๐Ÿฐ ๐Ÿฏ ๐ŸŸ ๐ŸŽก ๐ŸŽข ๐ŸŽ  โ›ฒ๏ธ โ›ฑ ๐Ÿ– ๐Ÿ ๐Ÿœ ๐ŸŒ‹ โ›ฐ ๐Ÿ” ๐Ÿ—ป ๐Ÿ• โ›บ๏ธ ๐Ÿ  ๐Ÿก ๐Ÿ˜ ๐Ÿš ๐Ÿ— ๐Ÿญ ๐Ÿข ๐Ÿฌ ๐Ÿฃ ๐Ÿค ๐Ÿฅ ๐Ÿฆ ๐Ÿจ ๐Ÿช ๐Ÿซ ๐Ÿฉ ๐Ÿ’’ ๐Ÿ› โ›ช๏ธ ๐Ÿ•Œ ๐Ÿ• ๐Ÿ•‹ โ›ฉ ๐Ÿ›ค ๐Ÿ›ฃ ๐Ÿ—พ ๐ŸŽ‘ ๐Ÿž ๐ŸŒ… ๐ŸŒ„ ๐ŸŒ  ๐ŸŽ‡ ๐ŸŽ† ๐ŸŒ‡ ๐ŸŒ† ๐Ÿ™ ๐ŸŒƒ ๐ŸŒŒ ๐ŸŒ‰ ๐ŸŒโŒš๏ธ ๐Ÿ“ฑ ๐Ÿ“ฒ ๐Ÿ’ป โŒจ๏ธ ๐Ÿ–ฅ ๐Ÿ–จ ๐Ÿ–ฑ ๐Ÿ–ฒ ๐Ÿ•น ๐Ÿ—œ ๐Ÿ’ฝ ๐Ÿ’พ ๐Ÿ’ฟ ๐Ÿ“€ ๐Ÿ“ผ ๐Ÿ“ท ๐Ÿ“ธ ๐Ÿ“น ๐ŸŽฅ ๐Ÿ“ฝ ๐ŸŽž ๐Ÿ“ž โ˜Ž๏ธ ๐Ÿ“Ÿ ๐Ÿ“  ๐Ÿ“บ ๐Ÿ“ป ๐ŸŽ™ ๐ŸŽš ๐ŸŽ› โฑ โฒ โฐ ๐Ÿ•ฐ โŒ›๏ธ โณ ๐Ÿ“ก ๐Ÿ”‹ ๐Ÿ”Œ ๐Ÿ’ก ๐Ÿ”ฆ ๐Ÿ•ฏ ๐Ÿ—‘ ๐Ÿ›ข ๐Ÿ’ธ ๐Ÿ’ต ๐Ÿ’ด ๐Ÿ’ถ ๐Ÿ’ท ๐Ÿ’ฐ ๐Ÿ’ณ ๐Ÿงพ ๐Ÿ’Ž โš–๏ธ ๐Ÿ”ง ๐Ÿ”จ โš’ ๐Ÿ›  โ› ๐Ÿ”ฉ โš™๏ธ โ›“ ๐Ÿ”ซ ๐Ÿ’ฃ ๐Ÿ”ช ๐Ÿ—ก โš”๏ธ ๐Ÿ›ก ๐Ÿšฌ โšฐ๏ธ โšฑ๏ธ ๐Ÿบ ๐Ÿงญ ๐Ÿงฑ ๐Ÿ”ฎ ๐Ÿงฟ ๐Ÿงธ ๐Ÿ“ฟ ๐Ÿ’ˆ โš—๏ธ ๐Ÿ”ญ ๐Ÿงฐ ๐Ÿงฒ ๐Ÿงช ๐Ÿงซ ๐Ÿงฌ ๐Ÿงฏ ๐Ÿ”ฌ ๐Ÿ•ณ ๐Ÿ’Š ๐Ÿ’‰ ๐ŸŒก ๐Ÿšฝ ๐Ÿšฐ ๐Ÿšฟ ๐Ÿ› ๐Ÿ›€ ๐Ÿ›€๐Ÿป ๐Ÿ›€๐Ÿผ ๐Ÿ›€๐Ÿฝ ๐Ÿ›€๐Ÿพ ๐Ÿ›€๐Ÿฟ ๐Ÿงด ๐Ÿงต ๐Ÿงถ ๐Ÿงท ๐Ÿงน ๐Ÿงบ ๐Ÿงป ๐Ÿงผ ๐Ÿงฝ ๐Ÿ›Ž ๐Ÿ”‘ ๐Ÿ— ๐Ÿšช ๐Ÿ›‹ ๐Ÿ› ๐Ÿ›Œ ๐Ÿ–ผ ๐Ÿ› ๐Ÿงณ ๐Ÿ›’ ๐ŸŽ ๐ŸŽˆ ๐ŸŽ ๐ŸŽ€ ๐ŸŽŠ ๐ŸŽ‰ ๐Ÿงจ ๐ŸŽŽ ๐Ÿฎ ๐ŸŽ ๐Ÿงง โœ‰๏ธ ๐Ÿ“ฉ ๐Ÿ“จ ๐Ÿ“ง ๐Ÿ’Œ ๐Ÿ“ฅ ๐Ÿ“ค ๐Ÿ“ฆ ๐Ÿท ๐Ÿ“ช ๐Ÿ“ซ ๐Ÿ“ฌ ๐Ÿ“ญ ๐Ÿ“ฎ ๐Ÿ“ฏ ๐Ÿ“œ ๐Ÿ“ƒ ๐Ÿ“„ ๐Ÿ“‘ ๐Ÿ“Š ๐Ÿ“ˆ ๐Ÿ“‰ ๐Ÿ—’ ๐Ÿ—“ ๐Ÿ“† ๐Ÿ“… ๐Ÿ“‡ ๐Ÿ—ƒ ๐Ÿ—ณ ๐Ÿ—„ ๐Ÿ“‹ ๐Ÿ“ ๐Ÿ“‚ ๐Ÿ—‚ ๐Ÿ—ž ๐Ÿ“ฐ ๐Ÿ““ ๐Ÿ“” ๐Ÿ“’ ๐Ÿ“• ๐Ÿ“— ๐Ÿ“˜ ๐Ÿ“™ ๐Ÿ“š ๐Ÿ“– ๐Ÿ”– ๐Ÿ”— ๐Ÿ“Ž ๐Ÿ–‡ ๐Ÿ“ ๐Ÿ“ ๐Ÿ“Œ ๐Ÿ“ โœ‚๏ธ ๐Ÿ–Š ๐Ÿ–‹ โœ’๏ธ ๐Ÿ–Œ ๐Ÿ– ๐Ÿ“ โœ๏ธ ๐Ÿ” ๐Ÿ”Ž ๐Ÿ” ๐Ÿ” ๐Ÿ”’ ๐Ÿ”“โค๏ธ ๐Ÿงก ๐Ÿ’› ๐Ÿ’š ๐Ÿ’™ ๐Ÿ’œ ๐Ÿ–ค ๐Ÿ’” โฃ๏ธ ๐Ÿ’• ๐Ÿ’ž ๐Ÿ’“ ๐Ÿ’— ๐Ÿ’– ๐Ÿ’˜ ๐Ÿ’ ๐Ÿ’Ÿ โ˜ฎ๏ธ โœ๏ธ โ˜ช๏ธ ๐Ÿ•‰ โ˜ธ๏ธ โœก๏ธ ๐Ÿ”ฏ ๐Ÿ•Ž โ˜ฏ๏ธ โ˜ฆ๏ธ ๐Ÿ› โ›Ž โ™ˆ๏ธ โ™‰๏ธ โ™Š๏ธ โ™‹๏ธ โ™Œ๏ธ โ™๏ธ โ™Ž๏ธ โ™๏ธ โ™๏ธ โ™‘๏ธ โ™’๏ธ โ™“๏ธ ๐Ÿ†” โš›๏ธ ๐Ÿ‰‘ โ˜ข๏ธ โ˜ฃ๏ธ ๐Ÿ“ด ๐Ÿ“ณ ๐Ÿˆถ ๐Ÿˆš๏ธ ๐Ÿˆธ ๐Ÿˆบ ๐Ÿˆท๏ธ โœด๏ธ ๐Ÿ†š ๐Ÿ’ฎ ๐Ÿ‰ ใŠ™๏ธ ใŠ—๏ธ ๐Ÿˆด ๐Ÿˆต ๐Ÿˆน ๐Ÿˆฒ ๐Ÿ…ฐ๏ธ ๐Ÿ…ฑ๏ธ ๐Ÿ†Ž ๐Ÿ†‘ ๐Ÿ…พ๏ธ ๐Ÿ†˜ โŒ โญ•๏ธ ๐Ÿ›‘ โ›”๏ธ ๐Ÿ“› ๐Ÿšซ ๐Ÿ’ฏ ๐Ÿ’ข โ™จ๏ธ ๐Ÿšท ๐Ÿšฏ ๐Ÿšณ ๐Ÿšฑ ๐Ÿ”ž ๐Ÿ“ต ๐Ÿšญ โ—๏ธ โ• โ“ โ” โ€ผ๏ธ โ‰๏ธ ๐Ÿ”… ๐Ÿ”† ใ€ฝ๏ธ โš ๏ธ ๐Ÿšธ ๐Ÿ”ฑ โšœ๏ธ ๐Ÿ”ฐ โ™ป๏ธ โœ… ๐Ÿˆฏ๏ธ ๐Ÿ’น โ‡๏ธ โœณ๏ธ โŽ ๐ŸŒ ๐Ÿ’  โ“‚๏ธ ๐ŸŒ€ ๐Ÿ’ค ๐Ÿง ๐Ÿšพ โ™ฟ๏ธ ๐Ÿ…ฟ๏ธ ๐Ÿˆณ ๐Ÿˆ‚๏ธ ๐Ÿ›‚ ๐Ÿ›ƒ ๐Ÿ›„ ๐Ÿ›… ๐Ÿšน ๐Ÿšบ ๐Ÿšผ ๐Ÿšป ๐Ÿšฎ ๐ŸŽฆ ๐Ÿ“ถ ๐Ÿˆ ๐Ÿ”ฃ โ„น๏ธ ๐Ÿ”ค ๐Ÿ”ก ๐Ÿ”  ๐Ÿ†– ๐Ÿ†— ๐Ÿ†™ ๐Ÿ†’ ๐Ÿ†• ๐Ÿ†“ 0๏ธโƒฃ 1๏ธโƒฃ 2๏ธโƒฃ 3๏ธโƒฃ 4๏ธโƒฃ 5๏ธโƒฃ 6๏ธโƒฃ 7๏ธโƒฃ 8๏ธโƒฃ 9๏ธโƒฃ ๐Ÿ”Ÿ ๐Ÿ”ข #๏ธโƒฃ *๏ธโƒฃ โ๏ธ โ–ถ๏ธ โธ โฏ โน โบ โญ โฎ โฉ โช โซ โฌ โ—€๏ธ ๐Ÿ”ผ ๐Ÿ”ฝ โžก๏ธ โฌ…๏ธ โฌ†๏ธ โฌ‡๏ธ โ†—๏ธ โ†˜๏ธ โ†™๏ธ โ†–๏ธ โ†•๏ธ โ†”๏ธ โ†ช๏ธ โ†ฉ๏ธ โคด๏ธ โคต๏ธ ๐Ÿ”€ ๐Ÿ” ๐Ÿ”‚ ๐Ÿ”„ ๐Ÿ”ƒ ๐ŸŽต ๐ŸŽถ โž• โž– โž— โœ–๏ธ โ™พ ๐Ÿ’ฒ ๐Ÿ’ฑ โ„ข๏ธ ยฉ๏ธ ยฎ๏ธ ใ€ฐ๏ธ โžฐ โžฟ ๐Ÿ”š ๐Ÿ”™ ๐Ÿ”› ๐Ÿ” ๐Ÿ”œ โœ”๏ธ โ˜‘๏ธ ๐Ÿ”˜ โšช๏ธ โšซ๏ธ ๐Ÿ”ด ๐Ÿ”ต ๐Ÿ”บ ๐Ÿ”ป ๐Ÿ”ธ ๐Ÿ”น ๐Ÿ”ถ ๐Ÿ”ท ๐Ÿ”ณ ๐Ÿ”ฒ โ–ช๏ธ โ–ซ๏ธ โ—พ๏ธ โ—ฝ๏ธ โ—ผ๏ธ โ—ป๏ธ โฌ›๏ธ โฌœ๏ธ ๐Ÿ”ˆ ๐Ÿ”‡ ๐Ÿ”‰ ๐Ÿ”Š ๐Ÿ”” ๐Ÿ”• ๐Ÿ“ฃ ๐Ÿ“ข ๐Ÿ‘โ€๐Ÿ—จ ๐Ÿ’ฌ ๐Ÿ’ญ ๐Ÿ—ฏ โ™ ๏ธ โ™ฃ๏ธ โ™ฅ๏ธ โ™ฆ๏ธ ๐Ÿƒ ๐ŸŽด ๐Ÿ€„๏ธ ๐Ÿ• ๐Ÿ•‘ ๐Ÿ•’ ๐Ÿ•“ ๐Ÿ•” ๐Ÿ•• ๐Ÿ•– ๐Ÿ•— ๐Ÿ•˜ ๐Ÿ•™ ๐Ÿ•š ๐Ÿ•› ๐Ÿ•œ ๐Ÿ• ๐Ÿ•ž ๐Ÿ•Ÿ ๐Ÿ•  ๐Ÿ•ก ๐Ÿ•ข ๐Ÿ•ฃ ๐Ÿ•ค ๐Ÿ•ฅ ๐Ÿ•ฆ ๐Ÿ•ง๐Ÿณ๏ธ ๐Ÿด ๐Ÿ ๐Ÿšฉ ๐Ÿณ๏ธโ€๐ŸŒˆ ๐Ÿดโ€โ˜ ๏ธ ๐Ÿ‡ฆ๐Ÿ‡ซ ๐Ÿ‡ฆ๐Ÿ‡ฝ ๐Ÿ‡ฆ๐Ÿ‡ฑ ๐Ÿ‡ฉ๐Ÿ‡ฟ ๐Ÿ‡ฆ๐Ÿ‡ธ ๐Ÿ‡ฆ๐Ÿ‡ฉ ๐Ÿ‡ฆ๐Ÿ‡ด ๐Ÿ‡ฆ๐Ÿ‡ฎ ๐Ÿ‡ฆ๐Ÿ‡ถ ๐Ÿ‡ฆ๐Ÿ‡ฌ ๐Ÿ‡ฆ๐Ÿ‡ท ๐Ÿ‡ฆ๐Ÿ‡ฒ ๐Ÿ‡ฆ๐Ÿ‡ผ ๐Ÿ‡ฆ๐Ÿ‡บ ๐Ÿ‡ฆ๐Ÿ‡น ๐Ÿ‡ฆ๐Ÿ‡ฟ ๐Ÿ‡ง๐Ÿ‡ธ ๐Ÿ‡ง๐Ÿ‡ญ ๐Ÿ‡ง๐Ÿ‡ฉ ๐Ÿ‡ง๐Ÿ‡ง ๐Ÿ‡ง๐Ÿ‡พ ๐Ÿ‡ง๐Ÿ‡ช ๐Ÿ‡ง๐Ÿ‡ฟ ๐Ÿ‡ง๐Ÿ‡ฏ ๐Ÿ‡ง๐Ÿ‡ฒ ๐Ÿ‡ง๐Ÿ‡น ๐Ÿ‡ง๐Ÿ‡ด ๐Ÿ‡ง๐Ÿ‡ฆ ๐Ÿ‡ง๐Ÿ‡ผ ๐Ÿ‡ง๐Ÿ‡ท ๐Ÿ‡ฎ๐Ÿ‡ด ๐Ÿ‡ป๐Ÿ‡ฌ ๐Ÿ‡ง๐Ÿ‡ณ ๐Ÿ‡ง๐Ÿ‡ฌ ๐Ÿ‡ง๐Ÿ‡ซ ๐Ÿ‡ง๐Ÿ‡ฎ ๐Ÿ‡ฐ๐Ÿ‡ญ ๐Ÿ‡จ๐Ÿ‡ฒ ๐Ÿ‡จ๐Ÿ‡ฆ ๐Ÿ‡ฎ๐Ÿ‡จ ๐Ÿ‡จ๐Ÿ‡ป ๐Ÿ‡ง๐Ÿ‡ถ ๐Ÿ‡ฐ๐Ÿ‡พ ๐Ÿ‡จ๐Ÿ‡ซ ๐Ÿ‡น๐Ÿ‡ฉ ๐Ÿ‡จ๐Ÿ‡ฑ ๐Ÿ‡จ๐Ÿ‡ณ ๐Ÿ‡จ๐Ÿ‡ฝ ๐Ÿ‡จ๐Ÿ‡จ ๐Ÿ‡จ๐Ÿ‡ด ๐Ÿ‡ฐ๐Ÿ‡ฒ ๐Ÿ‡จ๐Ÿ‡ฌ ๐Ÿ‡จ๐Ÿ‡ฉ ๐Ÿ‡จ๐Ÿ‡ฐ ๐Ÿ‡จ๐Ÿ‡ท ๐Ÿ‡จ๐Ÿ‡ฎ ๐Ÿ‡ญ๐Ÿ‡ท ๐Ÿ‡จ๐Ÿ‡บ ๐Ÿ‡จ๐Ÿ‡ผ ๐Ÿ‡จ๐Ÿ‡พ ๐Ÿ‡จ๐Ÿ‡ฟ ๐Ÿ‡ฉ๐Ÿ‡ฐ ๐Ÿ‡ฉ๐Ÿ‡ฏ ๐Ÿ‡ฉ๐Ÿ‡ฒ ๐Ÿ‡ฉ๐Ÿ‡ด ๐Ÿ‡ช๐Ÿ‡จ ๐Ÿ‡ช๐Ÿ‡ฌ ๐Ÿ‡ธ๐Ÿ‡ป ๐Ÿ‡ฌ๐Ÿ‡ถ ๐Ÿ‡ช๐Ÿ‡ท ๐Ÿ‡ช๐Ÿ‡ช ๐Ÿ‡ช๐Ÿ‡น ๐Ÿ‡ช๐Ÿ‡บ ๐Ÿ‡ซ๐Ÿ‡ฐ ๐Ÿ‡ซ๐Ÿ‡ด ๐Ÿ‡ซ๐Ÿ‡ฏ ๐Ÿ‡ซ๐Ÿ‡ฎ ๐Ÿ‡ซ๐Ÿ‡ท ๐Ÿ‡ฌ๐Ÿ‡ซ ๐Ÿ‡ต๐Ÿ‡ซ ๐Ÿ‡น๐Ÿ‡ซ ๐Ÿ‡ฌ๐Ÿ‡ฆ ๐Ÿ‡ฌ๐Ÿ‡ฒ ๐Ÿ‡ฌ๐Ÿ‡ช ๐Ÿ‡ฉ๐Ÿ‡ช ๐Ÿ‡ฌ๐Ÿ‡ญ ๐Ÿ‡ฌ๐Ÿ‡ฎ ๐Ÿ‡ฌ๐Ÿ‡ท ๐Ÿ‡ฌ๐Ÿ‡ฑ ๐Ÿ‡ฌ๐Ÿ‡ฉ ๐Ÿ‡ฌ๐Ÿ‡ต ๐Ÿ‡ฌ๐Ÿ‡บ ๐Ÿ‡ฌ๐Ÿ‡น ๐Ÿ‡ฌ๐Ÿ‡ฌ ๐Ÿ‡ฌ๐Ÿ‡ณ ๐Ÿ‡ฌ๐Ÿ‡ผ ๐Ÿ‡ฌ๐Ÿ‡พ ๐Ÿ‡ญ๐Ÿ‡น ๐Ÿ‡ญ๐Ÿ‡ณ ๐Ÿ‡ญ๐Ÿ‡ฐ ๐Ÿ‡ญ๐Ÿ‡บ ๐Ÿ‡ฎ๐Ÿ‡ธ ๐Ÿ‡ฎ๐Ÿ‡ณ ๐Ÿ‡ฎ๐Ÿ‡ฉ ๐Ÿ‡ฎ๐Ÿ‡ท ๐Ÿ‡ฎ๐Ÿ‡ถ ๐Ÿ‡ฎ๐Ÿ‡ช ๐Ÿ‡ฎ๐Ÿ‡ฒ ๐Ÿ‡ฎ๐Ÿ‡ฑ ๐Ÿ‡ฎ๐Ÿ‡น ๐Ÿ‡ฏ๐Ÿ‡ฒ ๐Ÿ‡ฏ๐Ÿ‡ต ๐ŸŽŒ ๐Ÿ‡ฏ๐Ÿ‡ช ๐Ÿ‡ฏ๐Ÿ‡ด ๐Ÿ‡ฐ๐Ÿ‡ฟ ๐Ÿ‡ฐ๐Ÿ‡ช ๐Ÿ‡ฐ๐Ÿ‡ฎ ๐Ÿ‡ฝ๐Ÿ‡ฐ ๐Ÿ‡ฐ๐Ÿ‡ผ ๐Ÿ‡ฐ๐Ÿ‡ฌ ๐Ÿ‡ฑ๐Ÿ‡ฆ ๐Ÿ‡ฑ๐Ÿ‡ป ๐Ÿ‡ฑ๐Ÿ‡ง ๐Ÿ‡ฑ๐Ÿ‡ธ ๐Ÿ‡ฑ๐Ÿ‡ท ๐Ÿ‡ฑ๐Ÿ‡พ ๐Ÿ‡ฑ๐Ÿ‡ฎ ๐Ÿ‡ฑ๐Ÿ‡น ๐Ÿ‡ฑ๐Ÿ‡บ ๐Ÿ‡ฒ๐Ÿ‡ด ๐Ÿ‡ฒ๐Ÿ‡ฐ ๐Ÿ‡ฒ๐Ÿ‡ฌ ๐Ÿ‡ฒ๐Ÿ‡ผ ๐Ÿ‡ฒ๐Ÿ‡พ ๐Ÿ‡ฒ๐Ÿ‡ป ๐Ÿ‡ฒ๐Ÿ‡ฑ ๐Ÿ‡ฒ๐Ÿ‡น ๐Ÿ‡ฒ๐Ÿ‡ญ ๐Ÿ‡ฒ๐Ÿ‡ถ ๐Ÿ‡ฒ๐Ÿ‡ท ๐Ÿ‡ฒ๐Ÿ‡บ ๐Ÿ‡พ๐Ÿ‡น ๐Ÿ‡ฒ๐Ÿ‡ฝ ๐Ÿ‡ซ๐Ÿ‡ฒ ๐Ÿ‡ฒ๐Ÿ‡ฉ ๐Ÿ‡ฒ๐Ÿ‡จ ๐Ÿ‡ฒ๐Ÿ‡ณ ๐Ÿ‡ฒ๐Ÿ‡ช ๐Ÿ‡ฒ๐Ÿ‡ธ ๐Ÿ‡ฒ๐Ÿ‡ฆ ๐Ÿ‡ฒ๐Ÿ‡ฟ ๐Ÿ‡ฒ๐Ÿ‡ฒ ๐Ÿ‡ณ๐Ÿ‡ฆ ๐Ÿ‡ณ๐Ÿ‡ท ๐Ÿ‡ณ๐Ÿ‡ต ๐Ÿ‡ณ๐Ÿ‡ฑ ๐Ÿ‡ณ๐Ÿ‡จ ๐Ÿ‡ณ๐Ÿ‡ฟ ๐Ÿ‡ณ๐Ÿ‡ฎ ๐Ÿ‡ณ๐Ÿ‡ช ๐Ÿ‡ณ๐Ÿ‡ฌ ๐Ÿ‡ณ๐Ÿ‡บ ๐Ÿ‡ณ๐Ÿ‡ซ ๐Ÿ‡ฐ๐Ÿ‡ต ๐Ÿ‡ฒ๐Ÿ‡ต ๐Ÿ‡ณ๐Ÿ‡ด ๐Ÿ‡ด๐Ÿ‡ฒ ๐Ÿ‡ต๐Ÿ‡ฐ ๐Ÿ‡ต๐Ÿ‡ผ ๐Ÿ‡ต๐Ÿ‡ธ ๐Ÿ‡ต๐Ÿ‡ฆ ๐Ÿ‡ต๐Ÿ‡ฌ ๐Ÿ‡ต๐Ÿ‡พ ๐Ÿ‡ต๐Ÿ‡ช ๐Ÿ‡ต๐Ÿ‡ญ ๐Ÿ‡ต๐Ÿ‡ณ ๐Ÿ‡ต๐Ÿ‡ฑ ๐Ÿ‡ต๐Ÿ‡น ๐Ÿ‡ต๐Ÿ‡ท ๐Ÿ‡ถ๐Ÿ‡ฆ ๐Ÿ‡ท๐Ÿ‡ช ๐Ÿ‡ท๐Ÿ‡ด ๐Ÿ‡ท๐Ÿ‡บ ๐Ÿ‡ท๐Ÿ‡ผ ๐Ÿ‡ผ๐Ÿ‡ธ ๐Ÿ‡ธ๐Ÿ‡ฒ ๐Ÿ‡ธ๐Ÿ‡ฆ ๐Ÿ‡ธ๐Ÿ‡ณ ๐Ÿ‡ท๐Ÿ‡ธ ๐Ÿ‡ธ๐Ÿ‡จ ๐Ÿ‡ธ๐Ÿ‡ฑ ๐Ÿ‡ธ๐Ÿ‡ฌ ๐Ÿ‡ธ๐Ÿ‡ฝ ๐Ÿ‡ธ๐Ÿ‡ฐ ๐Ÿ‡ธ๐Ÿ‡ฎ ๐Ÿ‡ฌ๐Ÿ‡ธ ๐Ÿ‡ธ๐Ÿ‡ง ๐Ÿ‡ธ๐Ÿ‡ด ๐Ÿ‡ฟ๐Ÿ‡ฆ ๐Ÿ‡ฐ๐Ÿ‡ท ๐Ÿ‡ธ๐Ÿ‡ธ ๐Ÿ‡ช๐Ÿ‡ธ ๐Ÿ‡ฑ๐Ÿ‡ฐ ๐Ÿ‡ง๐Ÿ‡ฑ ๐Ÿ‡ธ๐Ÿ‡ญ ๐Ÿ‡ฐ๐Ÿ‡ณ ๐Ÿ‡ฑ๐Ÿ‡จ ๐Ÿ‡ต๐Ÿ‡ฒ ๐Ÿ‡ป๐Ÿ‡จ ๐Ÿ‡ธ๐Ÿ‡ฉ ๐Ÿ‡ธ๐Ÿ‡ท ๐Ÿ‡ธ๐Ÿ‡ฟ ๐Ÿ‡ธ๐Ÿ‡ช ๐Ÿ‡จ๐Ÿ‡ญ ๐Ÿ‡ธ๐Ÿ‡พ ๐Ÿ‡น๐Ÿ‡ผ ๐Ÿ‡น๐Ÿ‡ฏ ๐Ÿ‡น๐Ÿ‡ฟ ๐Ÿ‡น๐Ÿ‡ญ ๐Ÿ‡น๐Ÿ‡ฑ ๐Ÿ‡น๐Ÿ‡ฌ ๐Ÿ‡น๐Ÿ‡ฐ ๐Ÿ‡น๐Ÿ‡ด ๐Ÿ‡น๐Ÿ‡น ๐Ÿ‡น๐Ÿ‡ณ ๐Ÿ‡น๐Ÿ‡ท ๐Ÿ‡น๐Ÿ‡ฒ ๐Ÿ‡น๐Ÿ‡จ ๐Ÿ‡น๐Ÿ‡ป ๐Ÿ‡ป๐Ÿ‡ฎ ๐Ÿ‡บ๐Ÿ‡ฌ ๐Ÿ‡บ๐Ÿ‡ฆ ๐Ÿ‡ฆ๐Ÿ‡ช ๐Ÿ‡ฌ๐Ÿ‡ง ๐Ÿด๓ ง๓ ข๓ ฅ๓ ฎ๓ ง๓ ฟ ๐Ÿด๓ ง๓ ข๓ ณ๓ ฃ๓ ด๓ ฟ ๐Ÿด๓ ง๓ ข๓ ท๓ ฌ๓ ณ๓ ฟ ๐Ÿ‡บ๐Ÿ‡ณ ๐Ÿ‡บ๐Ÿ‡ธ ๐Ÿ‡บ๐Ÿ‡พ ๐Ÿ‡บ๐Ÿ‡ฟ ๐Ÿ‡ป๐Ÿ‡บ ๐Ÿ‡ป๐Ÿ‡ฆ ๐Ÿ‡ป๐Ÿ‡ช ๐Ÿ‡ป๐Ÿ‡ณ ๐Ÿ‡ผ๐Ÿ‡ซ ๐Ÿ‡ช๐Ÿ‡ญ ๐Ÿ‡พ๐Ÿ‡ช ๐Ÿ‡ฟ๐Ÿ‡ฒ ๐Ÿ‡ฟ๐Ÿ‡ผ๐Ÿฅฑ ๐Ÿค ๐Ÿฆพ ๐Ÿฆฟ ๐Ÿฆป ๐Ÿง ๐Ÿงโ€โ™‚๏ธ ๐Ÿงโ€โ™€๏ธ ๐Ÿง ๐Ÿงโ€โ™‚๏ธ ๐Ÿงโ€โ™€๏ธ ๐ŸงŽ ๐ŸงŽโ€โ™‚๏ธ ๐ŸงŽโ€โ™€๏ธ ๐Ÿ‘จโ€๐Ÿฆฏ ๐Ÿ‘ฉโ€๐Ÿฆฏ ๐Ÿ‘จโ€๐Ÿฆผ ๐Ÿ‘ฉโ€๐Ÿฆผ ๐Ÿ‘จโ€๐Ÿฆฝ ๐Ÿ‘ฉโ€๐Ÿฆฝ ๐Ÿฆง ๐Ÿฆฎ ๐Ÿ•โ€๐Ÿฆบ ๐Ÿฆฅ ๐Ÿฆฆ ๐Ÿฆจ ๐Ÿฆฉ ๐Ÿง„ ๐Ÿง… ๐Ÿง‡ ๐Ÿง† ๐Ÿงˆ ๐Ÿฆช ๐Ÿงƒ ๐Ÿง‰ ๐ŸงŠ ๐Ÿ›• ๐Ÿฆฝ ๐Ÿฆผ ๐Ÿ›บ ๐Ÿช‚ ๐Ÿช ๐Ÿคฟ ๐Ÿช€ ๐Ÿช ๐Ÿฆบ ๐Ÿฅป ๐Ÿฉฑ ๐Ÿฉฒ ๐Ÿฉณ ๐Ÿฉฐ ๐Ÿช• ๐Ÿช” ๐Ÿช“ ๐Ÿฆฏ ๐Ÿฉธ ๐Ÿฉน ๐Ÿฉบ ๐Ÿช‘ ๐Ÿช’ ๐ŸคŽ ๐Ÿค ๐ŸŸ  ๐ŸŸก ๐ŸŸข ๐ŸŸฃ ๐ŸŸค ๐ŸŸฅ ๐ŸŸง ๐ŸŸจ ๐ŸŸฉ ๐ŸŸฆ ๐ŸŸช ๐ŸŸซ + + diff --git a/tests/backend/specs/api/fuzzImportTest.js b/tests/backend/specs/api/fuzzImportTest.js new file mode 100644 index 000000000..ca62a1bee --- /dev/null +++ b/tests/backend/specs/api/fuzzImportTest.js @@ -0,0 +1,75 @@ +/* + * Fuzz testing the import endpoint + */ +/* +const fs = require('fs'); +const settings = require(__dirname+'/../../../../tests/container/loadSettings.js').loadSettings(); +const host = "http://" + settings.ip + ":" + settings.port; +const path = require('path'); +const async = require(__dirname+'/../../../../src/node_modules/async'); +const request = require(__dirname+'/../../../../src/node_modules/request'); +const froth = require(__dirname+'/../../../../src/node_modules/mocha-froth'); + +var filePath = path.join(__dirname, '../../../../APIKEY.txt'); +var apiKey = fs.readFileSync(filePath, {encoding: 'utf-8'}); +apiKey = apiKey.replace(/\n$/, ""); +var apiVersion = 1; +var testPadId = "TEST_fuzz" + makeid(); + +var endPoint = function(point, version){ + version = version || apiVersion; + return '/api/'+version+'/'+point+'?apikey='+apiKey; +} + +//console.log("Testing against padID", testPadId); +//console.log("To watch the test live visit " + host + "/p/" + testPadId); +//console.log("Tests will start in 5 seconds, click the URL now!"); + +setTimeout(function(){ + for (let i=1; i<5; i++) { // 5000 runs + setTimeout( function timer(){ + runTest(i); + }, i*100 ); // 100 ms + } + process.exit(0); +},5000); // wait 5 seconds + +function runTest(number){ + request(host + endPoint('createPad') + '&padID=' + testPadId, function(err, res, body){ + var req = request.post(host + '/p/'+testPadId+'/import', function (err, res, body) { + if (err) { + throw new Error("FAILURE", err); + }else{ + console.log("Success"); + } + }); + + var fN = '/tmp/fuzztest.txt'; + var cT = 'text/plain'; + + if (number % 2 == 0) { + fN = froth().toString(); + cT = froth().toString(); + } + + let form = req.form(); + + form.append('file', froth().toString(), { + filename: fN, + contentType: cT + }); +console.log("here"); + }); +} + +function makeid() +{ + var text = ""; + var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + + for( var i=0; i < 5; i++ ){ + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; +} +*/ diff --git a/tests/backend/specs/api/image.png b/tests/backend/specs/api/image.png new file mode 100644 index 000000000..0694e2c2d Binary files /dev/null and b/tests/backend/specs/api/image.png differ diff --git a/tests/backend/specs/api/importexport.js b/tests/backend/specs/api/importexport.js new file mode 100644 index 000000000..685bb7241 --- /dev/null +++ b/tests/backend/specs/api/importexport.js @@ -0,0 +1,188 @@ +/* + * ACHTUNG: there is a copied & modified version of this file in + * /tests/container/spacs/api/pad.js + * + * TODO: unify those two files, and merge in a single one. + */ + +const assert = require('assert'); +const supertest = require(__dirname+'/../../../../src/node_modules/supertest'); +const fs = require('fs'); +const settings = require(__dirname+'/../../../../tests/container/loadSettings.js').loadSettings(); +const api = supertest('http://'+settings.ip+":"+settings.port); +const path = require('path'); +const async = require(__dirname+'/../../../../src/node_modules/async'); + +var filePath = path.join(__dirname, '../../../../APIKEY.txt'); + +var apiKey = fs.readFileSync(filePath, {encoding: 'utf-8'}); +apiKey = apiKey.replace(/\n$/, ""); +var apiVersion = 1; +var lastEdited = ""; + +var testImports = { + "malformed": { + input: '
  • wtf', + expectedHTML: 'wtf

    ', + expectedText: 'wtf\n\n' + }, + "nonelistiteminlist #3620":{ + input: '
      test
    • FOO
    ', + expectedHTML: '
      test
    • FOO

    ', + expectedText: '\ttest\n\t* FOO\n\n' + }, + "whitespaceinlist #3620":{ + input: '
    • FOO
    ', + expectedHTML: '
    • FOO

    ', + expectedText: '\t* FOO\n\n' + }, + "prefixcorrectlinenumber":{ + input: '
    1. should be 1
    2. should be 2
    ', + expectedHTML: '
    1. should be 1
    2. should be 2

    ', + expectedText: '\t1. should be 1\n\t2. should be 2\n\n' + }, + "prefixcorrectlinenumbernested":{ + input: '
    1. should be 1
      1. foo
    2. should be 2
    ', + expectedHTML: '
    1. should be 1
      1. foo
    2. should be 2

    ', + expectedText: '\t1. should be 1\n\t\t1.1. foo\n\t2. should be 2\n\n' + }, + + /* + "prefixcorrectlinenumber when introduced none list item - currently not supported see #3450":{ + input: '
    1. should be 1
    2. test
    3. should be 2
    ', + expectedHTML: '
    1. should be 1
    2. test
    3. should be 2

    ', + expectedText: '\t1. should be 1\n\ttest\n\t2. should be 2\n\n' + } + , + "newlinesshouldntresetlinenumber #2194":{ + input: '
    1. should be 1
    2. test
    3. should be 2
    ', + expectedHTML: '
    1. should be 1
    2. test
    3. should be 2

    ', + expectedText: '\t1. should be 1\n\ttest\n\t2. should be 2\n\n' + } + */ +} + +Object.keys(testImports).forEach(function (testName) { + var testPadId = makeid(); + test = testImports[testName]; + describe('createPad', function(){ + it('creates a new Pad', function(done) { + api.get(endPoint('createPad')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Unable to create new Pad"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); + }) + + describe('setHTML', function(){ + it('Sets the HTML', function(done) { + api.get(endPoint('setHTML')+"&padID="+testPadId+"&html="+test.input) + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Error:"+testName) + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); + }) + + describe('getHTML', function(){ + it('Gets back the HTML of a Pad', function(done) { + api.get(endPoint('getHTML')+"&padID="+testPadId) + .expect(function(res){ + var receivedHtml = res.body.data.html; + if (receivedHtml !== test.expectedHTML) { + throw new Error(`HTML received from export is not the one we were expecting. + Test Name: + ${testName} + + Received: + ${receivedHtml} + + Expected: + ${test.expectedHTML} + + Which is a different version of the originally imported one: + ${test.input}`); + } + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); + }) + + describe('getText', function(){ + it('Gets back the Text of a Pad', function(done) { + api.get(endPoint('getText')+"&padID="+testPadId) + .expect(function(res){ + var receivedText = res.body.data.text; + if (receivedText !== test.expectedText) { + throw new Error(`Text received from export is not the one we were expecting. + Test Name: + ${testName} + + Received: + ${receivedText} + + Expected: + ${test.expectedText} + + Which is a different version of the originally imported one: + ${test.input}`); + } + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); + }) +}); + + +var endPoint = function(point, version){ + version = version || apiVersion; + return '/api/'+version+'/'+point+'?apikey='+apiKey; +} + +function makeid() +{ + var text = ""; + var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + + for( var i=0; i < 5; i++ ){ + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; +} + +function generateLongText(){ + var text = ""; + var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + + for( var i=0; i < 80000; i++ ){ + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; +} + +// Need this to compare arrays (listSavedRevisions test) +Array.prototype.equals = function (array) { + // if the other array is a falsy value, return + if (!array) + return false; + // compare lengths - can save a lot of time + if (this.length != array.length) + return false; + for (var i = 0, l=this.length; i < l; i++) { + // Check if we have nested arrays + if (this[i] instanceof Array && array[i] instanceof Array) { + // recurse into the nested arrays + if (!this[i].equals(array[i])) + return false; + } else if (this[i] != array[i]) { + // Warning - two different object instances will never be equal: {x:20} != {x:20} + return false; + } + } + return true; +} diff --git a/tests/backend/specs/api/importexportGetPost.js b/tests/backend/specs/api/importexportGetPost.js new file mode 100644 index 000000000..e4e07639a --- /dev/null +++ b/tests/backend/specs/api/importexportGetPost.js @@ -0,0 +1,410 @@ +/* + * Import and Export tests for the /p/whateverPadId/import and /p/whateverPadId/export endpoints. + */ + +const assert = require('assert'); +const supertest = require(__dirname+'/../../../../src/node_modules/supertest'); +const fs = require('fs'); +const settings = require(__dirname+'/../../../../src/node/utils/Settings'); +const host = 'http://127.0.0.1:'+settings.port; +const api = supertest('http://'+settings.ip+":"+settings.port); +const path = require('path'); +const async = require(__dirname+'/../../../../src/node_modules/async'); +const request = require(__dirname+'/../../../../src/node_modules/request'); +const padText = fs.readFileSync("../tests/backend/specs/api/test.txt"); +const etherpadDoc = fs.readFileSync("../tests/backend/specs/api/test.etherpad"); +const wordDoc = fs.readFileSync("../tests/backend/specs/api/test.doc"); +const wordXDoc = fs.readFileSync("../tests/backend/specs/api/test.docx"); +const odtDoc = fs.readFileSync("../tests/backend/specs/api/test.odt"); +const pdfDoc = fs.readFileSync("../tests/backend/specs/api/test.pdf"); +var filePath = path.join(__dirname, '../../../../APIKEY.txt'); + +var apiKey = fs.readFileSync(filePath, {encoding: 'utf-8'}); +apiKey = apiKey.replace(/\n$/, ""); +var apiVersion = 1; +var testPadId = makeid(); +var lastEdited = ""; +var text = generateLongText(); + +describe('Connectivity', function(){ + it('can connect', function(done) { + api.get('/api/') + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('API Versioning', function(){ + it('finds the version tag', function(done) { + api.get('/api/') + .expect(function(res){ + apiVersion = res.body.currentVersion; + if (!res.body.currentVersion) throw new Error("No version set in API"); + return; + }) + .expect(200, done) + }); +}) + +/* +Tests +----- + +Test. + / Create a pad + / Set pad contents + / Try export pad in various formats + / Get pad contents and ensure it matches imported contents + +Test. + / Try to export a pad that doesn't exist // Expect failure + +Test. + / Try to import an unsupported file to a pad that exists + +-- TODO: Test. + Try to import to a file and abort it half way through + +Test. + Try to import to files of varying size. + +Example Curl command for testing import URI: + curl -s -v --form file=@/home/jose/test.txt http://127.0.0.1:9001/p/foo/import +*/ + +describe('Imports and Exports', function(){ + it('creates a new Pad, imports content to it, checks that content', function(done) { + if(!settings.allowAnyoneToImport){ + console.warn("not anyone can import so not testing -- to include this test set allowAnyoneToImport to true in settings.json"); + done(); + }else{ + api.get(endPoint('createPad')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Unable to create new Pad"); + + var req = request.post(host + '/p/'+testPadId+'/import', function (err, res, body) { + if (err) { + throw new Error("Failed to import", err); + } else { + api.get(endPoint('getText')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.data.text !== padText.toString()){ + throw new Error("text is wrong on export"); + } + }) + } + }); + + let form = req.form(); + + form.append('file', padText, { + filename: '/test.txt', + contentType: 'text/plain' + }); + + }) + .expect('Content-Type', /json/) + .expect(200, done) + } + }); + + // For some reason word import does not work in testing.. + // TODO: fix support for .doc files.. + xit('Tries to import .doc that uses soffice or abiword', function(done) { + if(!settings.allowAnyoneToImport) return done(); + if((settings.abiword && settings.abiword.indexOf("/" === -1)) && (settings.office && settings.soffice.indexOf("/" === -1))) return done(); + + var req = request.post(host + '/p/'+testPadId+'/import', function (err, res, body) { + if (err) { + throw new Error("Failed to import", err); + } else { + if(res.body.indexOf("FrameCall('undefined', 'ok');") === -1){ + throw new Error("Failed DOC import", testPadId); + }else{ + done(); + } + } + }); + + let form = req.form(); + form.append('file', wordDoc, { + filename: '/test.doc', + contentType: 'application/msword' + }); + }); + + it('exports DOC', function(done) { + if(!settings.allowAnyoneToImport) return done(); + if((settings.abiword && settings.abiword.indexOf("/" === -1)) && (settings.office && settings.soffice.indexOf("/" === -1))) return done(); + try{ + request(host + '/p/'+testPadId+'/export/doc', function (err, res, body) { + // TODO: At some point checking that the contents is correct would be suitable + if(body.length >= 9000){ + done(); + }else{ + throw new Error("Word Document export length is not right"); + } + }) + }catch(e){ + throw new Error(e); + } + }) + + it('Tries to import .docx that uses soffice or abiword', function(done) { + if(!settings.allowAnyoneToImport) return done(); + if((settings.abiword && settings.abiword.indexOf("/" === -1)) && (settings.office && settings.soffice.indexOf("/" === -1))) return done(); + + var req = request.post(host + '/p/'+testPadId+'/import', function (err, res, body) { + if (err) { + throw new Error("Failed to import", err); + } else { + if(res.body.indexOf("FrameCall('undefined', 'ok');") === -1){ + throw new Error("Failed DOCX import"); + }else{ + done(); + } + } + }); + + let form = req.form(); + form.append('file', wordXDoc, { + filename: '/test.docx', + contentType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' + }); + }); + + it('exports DOC from imported DOCX', function(done) { + if(!settings.allowAnyoneToImport) return done(); + if((settings.abiword && settings.abiword.indexOf("/" === -1)) && (settings.office && settings.soffice.indexOf("/" === -1))) return done(); + request(host + '/p/'+testPadId+'/export/doc', function (err, res, body) { + // TODO: At some point checking that the contents is correct would be suitable + if(body.length >= 9100){ + done(); + }else{ + throw new Error("Word Document export length is not right"); + } + }) + }) + + it('Tries to import .pdf that uses soffice or abiword', function(done) { + if(!settings.allowAnyoneToImport) return done(); + if((settings.abiword && settings.abiword.indexOf("/" === -1)) && (settings.office && settings.soffice.indexOf("/" === -1))) return done(); + + var req = request.post(host + '/p/'+testPadId+'/import', function (err, res, body) { + if (err) { + throw new Error("Failed to import", err); + } else { + if(res.body.indexOf("FrameCall('undefined', 'ok');") === -1){ + throw new Error("Failed PDF import"); + }else{ + done(); + } + } + }); + + let form = req.form(); + form.append('file', pdfDoc, { + filename: '/test.pdf', + contentType: 'application/pdf' + }); + }); + + it('exports PDF', function(done) { + if(!settings.allowAnyoneToImport) return done(); + if((settings.abiword && settings.abiword.indexOf("/" === -1)) && (settings.office && settings.soffice.indexOf("/" === -1))) return done(); + request(host + '/p/'+testPadId+'/export/pdf', function (err, res, body) { + // TODO: At some point checking that the contents is correct would be suitable + if(body.length >= 1000){ + done(); + }else{ + throw new Error("PDF Document export length is not right"); + } + }) + }) + + it('Tries to import .odt that uses soffice or abiword', function(done) { + if(!settings.allowAnyoneToImport) return done(); + if((settings.abiword && settings.abiword.indexOf("/" === -1)) && (settings.office && settings.soffice.indexOf("/" === -1))) return done(); + + var req = request.post(host + '/p/'+testPadId+'/import', function (err, res, body) { + if (err) { + throw new Error("Failed to import", err); + } else { + if(res.body.indexOf("FrameCall('undefined', 'ok');") === -1){ + throw new Error("Failed ODT import", testPadId); + }else{ + done(); + } + } + }); + + let form = req.form(); + form.append('file', odtDoc, { + filename: '/test.odt', + contentType: 'application/odt' + }); + }); + + it('exports ODT', function(done) { + if(!settings.allowAnyoneToImport) return done(); + if((settings.abiword && settings.abiword.indexOf("/" === -1)) && (settings.office && settings.soffice.indexOf("/" === -1))) return done(); + request(host + '/p/'+testPadId+'/export/odt', function (err, res, body) { + // TODO: At some point checking that the contents is correct would be suitable + if(body.length >= 7000){ + done(); + }else{ + throw new Error("ODT Document export length is not right"); + } + }) + }) + + it('Tries to import .etherpad', function(done) { + if(!settings.allowAnyoneToImport) return done(); + + var req = request.post(host + '/p/'+testPadId+'/import', function (err, res, body) { + if (err) { + throw new Error("Failed to import", err); + } else { + if(res.body.indexOf("FrameCall(\'true\', \'ok\');") === -1){ + throw new Error("Failed Etherpad import", err, testPadId); + }else{ + done(); + } + } + }); + + let form = req.form(); + form.append('file', etherpadDoc, { + filename: '/test.etherpad', + contentType: 'application/etherpad' + }); + }); + + it('exports Etherpad', function(done) { + request(host + '/p/'+testPadId+'/export/etherpad', function (err, res, body) { + // TODO: At some point checking that the contents is correct would be suitable + if(body.indexOf("hello") !== -1){ + done(); + }else{ + console.error("body"); + throw new Error("Etherpad Document does not include hello"); + } + }) + }) + + it('exports HTML for this Etherpad file', function(done) { + request(host + '/p/'+testPadId+'/export/html', function (err, res, body) { + + // broken pre fix export --
      + var expectedHTML = '
        • hello
      '; + // expect body to include + if(body.indexOf(expectedHTML) !== -1){ + done(); + }else{ + console.error(body); + throw new Error("Exported HTML nested list items is not right", body); + } + }) + }) + + it('tries to import Plain Text to a pad that does not exist', function(done) { + var req = request.post(host + '/p/'+testPadId+testPadId+testPadId+'/import', function (err, res, body) { + if (res.statusCode === 200) { + throw new Error("Was able to import to a pad that doesn't exist"); + }else{ + // Wasn't able to write to a pad that doesn't exist, this is expected behavior + api.get(endPoint('getText')+"&padID="+testPadId+testPadId+testPadId) + .expect(function(res){ + if(res.body.code !== 1) throw new Error("Pad Exists"); + }) + .expect(200, done) + } + + let form = req.form(); + + form.append('file', padText, { + filename: '/test.txt', + contentType: 'text/plain' + }); + }) + }); + + it('Tries to import unsupported file type', function(done) { + if(settings.allowUnknownFileEnds === true){ + console.log("allowing unknown file ends so skipping this test"); + return done(); + } + + var req = request.post(host + '/p/'+testPadId+'/import', function (err, res, body) { + if (err) { + throw new Error("Failed to import", err); + } else { + if(res.body.indexOf("FrameCall('undefined', 'ok');") !== -1){ + console.log("worked"); + throw new Error("You shouldn't be able to import this file", testPadId); + } + return done(); + } + }); + + let form = req.form(); + form.append('file', padText, { + filename: '/test.xasdasdxx', + contentType: 'weirdness/jobby' + }); + }); + +// end of tests +}) + + + + + +var endPoint = function(point, version){ + version = version || apiVersion; + return '/api/'+version+'/'+point+'?apikey='+apiKey; +} + +function makeid() +{ + var text = ""; + var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + + for( var i=0; i < 5; i++ ){ + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; +} + +function generateLongText(){ + var text = ""; + var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + + for( var i=0; i < 80000; i++ ){ + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; +} + +// Need this to compare arrays (listSavedRevisions test) +Array.prototype.equals = function (array) { + // if the other array is a falsy value, return + if (!array) + return false; + // compare lengths - can save a lot of time + if (this.length != array.length) + return false; + for (var i = 0, l=this.length; i < l; i++) { + // Check if we have nested arrays + if (this[i] instanceof Array && array[i] instanceof Array) { + // recurse into the nested arrays + if (!this[i].equals(array[i])) + return false; + } else if (this[i] != array[i]) { + // Warning - two different object instances will never be equal: {x:20} != {x:20} + return false; + } + } + return true; +} diff --git a/tests/backend/specs/api/instance.js b/tests/backend/specs/api/instance.js new file mode 100644 index 000000000..4849c6507 --- /dev/null +++ b/tests/backend/specs/api/instance.js @@ -0,0 +1,54 @@ +/* + * Tests for the instance-level APIs + * + * Section "GLOBAL FUNCTIONS" in src/node/db/API.js + */ +const assert = require('assert'); +const supertest = require(__dirname+'/../../../../src/node_modules/supertest'); +const fs = require('fs'); +const settings = require(__dirname+'/../../../../src/node/utils/Settings'); +const api = supertest('http://'+settings.ip+":"+settings.port); +const path = require('path'); + +var filePath = path.join(__dirname, '../../../../APIKEY.txt'); + +var apiKey = fs.readFileSync(filePath, {encoding: 'utf-8'}); +apiKey = apiKey.replace(/\n$/, ""); + +var apiVersion = '1.2.14'; + +describe('Connectivity for instance-level API tests', function() { + it('can connect', function(done) { + api.get('/api/') + .expect('Content-Type', /json/) + .expect(200, done) + }); +}); + +describe('getStats', function(){ + it('Gets the stats of a running instance', function(done) { + api.get(endPoint('getStats')) + .expect(function(res){ + if (res.body.code !== 0) throw new Error("getStats() failed"); + + if (!(('totalPads' in res.body.data) && (typeof res.body.data.totalPads === 'number'))) { + throw new Error(`Response to getStats() does not contain field totalPads, or it's not a number: ${JSON.stringify(res.body.data)}`); + } + + if (!(('totalSessions' in res.body.data) && (typeof res.body.data.totalSessions === 'number'))) { + throw new Error(`Response to getStats() does not contain field totalSessions, or it's not a number: ${JSON.stringify(res.body.data)}`); + } + + if (!(('totalActivePads' in res.body.data) && (typeof res.body.data.totalActivePads === 'number'))) { + throw new Error(`Response to getStats() does not contain field totalActivePads, or it's not a number: ${JSON.stringify(res.body.data)}`); + } + }) + .expect('Content-Type', /json/) + .expect(200, done); + }); +}); + +var endPoint = function(point, version){ + version = version || apiVersion; + return '/api/'+version+'/'+point+'?apikey='+apiKey; +} diff --git a/tests/backend/specs/api/pad.js b/tests/backend/specs/api/pad.js index 26abfd2c8..95138c0b8 100644 --- a/tests/backend/specs/api/pad.js +++ b/tests/backend/specs/api/pad.js @@ -1,10 +1,17 @@ -var assert = require('assert') - supertest = require(__dirname+'/../../../../src/node_modules/supertest'), - fs = require('fs'), - settings = require(__dirname+'/../../loadSettings').loadSettings(), - api = supertest('http://'+settings.ip+":"+settings.port), - path = require('path'), - async = require(__dirname+'/../../../../src/node_modules/async'); +/* + * ACHTUNG: there is a copied & modified version of this file in + * /tests/container/specs/api/pad.js + * + * TODO: unify those two files, and merge in a single one. + */ + +const assert = require('assert'); +const supertest = require(__dirname+'/../../../../src/node_modules/supertest'); +const fs = require('fs'); +const settings = require(__dirname + '/../../../../src/node/utils/Settings'); +const api = supertest('http://'+settings.ip+":"+settings.port); +const path = require('path'); +const async = require(__dirname+'/../../../../src/node_modules/async'); var filePath = path.join(__dirname, '../../../../APIKEY.txt'); @@ -26,10 +33,23 @@ var ulHtml = '
      • one
      • two< * textually, but at least it remains standard compliant and has an equal DOM * structure. */ -var expectedHtml = '
        • one
        • two
        • 0
        • 1
        • 2
          • 3
          • 4
        1. item
          1. item1
          2. item2
        '; +var expectedHtml = '
        • one
        • two
        • 0
        • 1
        • 2
          • 3
          • 4
        1. item
          1. item1
          2. item2
        '; + +/* + * Html document with space between list items, to test its import and + * verify it is exported back correctly + */ +var ulSpaceHtml = '
        • one
        '; + +/* + * When exported back, Etherpad produces an html which is not exactly the same + * textually, but at least it remains standard compliant and has an equal DOM + * structure. + */ +var expectedSpaceHtml = '
        • one
        '; describe('Connectivity', function(){ - it('errors if can not connect', function(done) { + it('can connect', function(done) { api.get('/api/') .expect('Content-Type', /json/) .expect(200, done) @@ -37,7 +57,7 @@ describe('Connectivity', function(){ }) describe('API Versioning', function(){ - it('errors if can not connect', function(done) { + it('finds the version tag', function(done) { api.get('/api/') .expect(function(res){ apiVersion = res.body.currentVersion; @@ -49,7 +69,7 @@ describe('API Versioning', function(){ }) describe('Permission', function(){ - it('errors if can connect without correct APIKey', function(done) { + it('errors with invalid APIKey', function(done) { // This is broken because Etherpad doesn't handle HTTP codes properly see #2343 // If your APIKey is password you deserve to fail all tests anyway var permErrorURL = '/api/'+apiVersion+'/createPad?apikey=password&padID=test'; @@ -104,7 +124,7 @@ describe('deletePad', function(){ it('deletes a Pad', function(done) { api.get(endPoint('deletePad')+"&padID="+testPadId) .expect('Content-Type', /json/) - .expect(200, done) + .expect(200, done) // @TODO: we shouldn't expect 200 here since the pad may not exist }); }) @@ -166,6 +186,19 @@ describe('getHTML', function(){ }); }) +describe('listAllPads', function () { + it('list all pads', function (done) { + api.get(endPoint('listAllPads')) + .expect(function (res) { + if (res.body.data.padIDs.includes(testPadId) !== true) { + throw new Error('Unable to find pad in pad list') + } + }) + .expect('Content-Type', /json/) + .expect(200, done) + }) +}) + describe('deletePad', function(){ it('deletes a Pad', function(done) { api.get(endPoint('deletePad')+"&padID="+testPadId) @@ -177,6 +210,19 @@ describe('deletePad', function(){ }); }) +describe('listAllPads', function () { + it('list all pads', function (done) { + api.get(endPoint('listAllPads')) + .expect(function (res) { + if (res.body.data.padIDs.includes(testPadId) !== false) { + throw new Error('Test pad should not be in pads list') + } + }) + .expect('Content-Type', /json/) + .expect(200, done) + }) +}) + describe('getHTML', function(){ it('get the HTML of a Pad -- Should return a failure', function(done) { api.get(endPoint('getHTML')+"&padID="+testPadId) @@ -204,7 +250,7 @@ describe('getText', function(){ api.get(endPoint('getText')+"&padID="+testPadId) .expect(function(res){ if(res.body.data.text !== "testText\n") throw new Error("Pad Creation with text") - }) + }) .expect('Content-Type', /json/) .expect(200, done) }); @@ -212,7 +258,11 @@ describe('getText', function(){ describe('setText', function(){ it('creates a new Pad with text', function(done) { - api.get(endPoint('setText')+"&padID="+testPadId+"&text=testTextTwo") + api.post(endPoint('setText')) + .send({ + "padID": testPadId, + "text": "testTextTwo", + }) .expect(function(res){ if(res.body.code !== 0) throw new Error("Pad setting text failed"); }) @@ -327,7 +377,11 @@ describe('getLastEdited', function(){ describe('setText', function(){ it('creates a new Pad with text', function(done) { - api.get(endPoint('setText')+"&padID="+testPadId+"&text=testTextTwo") + api.post(endPoint('setText')) + .send({ + "padID": testPadId, + "text": "testTextTwo", + }) .expect(function(res){ if(res.body.code !== 0) throw new Error("Pad setting text failed"); }) @@ -387,7 +441,8 @@ describe('createPad', function(){ describe('setText', function(){ it('Sets text on a pad Id', function(done) { - api.get(endPoint('setText')+"&padID="+testPadId+"&text="+text) + api.post(endPoint('setText')+"&padID="+testPadId) + .field({text: text}) .expect(function(res){ if(res.body.code !== 0) throw new Error("Pad Set Text failed") }) @@ -410,7 +465,8 @@ describe('getText', function(){ describe('setText', function(){ it('Sets text on a pad Id including an explicit newline', function(done) { - api.get(endPoint('setText')+"&padID="+testPadId+"&text="+text+'%0A') + api.post(endPoint('setText')+"&padID="+testPadId) + .field({text: text+'\n'}) .expect(function(res){ if(res.body.code !== 0) throw new Error("Pad Set Text failed") }) @@ -524,9 +580,13 @@ describe('getText', function(){ describe('setHTML', function(){ it('Sets the HTML of a Pad attempting to pass ugly HTML', function(done) { var html = "
        Hello HTML
        "; - api.get(endPoint('setHTML')+"&padID="+testPadId+"&html="+html) + api.post(endPoint('setHTML')) + .send({ + "padID": testPadId, + "html": html, + }) .expect(function(res){ - if(res.body.code !== 1) throw new Error("Allowing crappy HTML to be imported") + if(res.body.code !== 0) throw new Error("Crappy HTML Can't be Imported[we weren't able to sanitize it']") }) .expect('Content-Type', /json/) .expect(200, done) @@ -535,7 +595,11 @@ describe('setHTML', function(){ describe('setHTML', function(){ it('Sets the HTML of a Pad with complex nested lists of different types', function(done) { - api.get(endPoint('setHTML')+"&padID="+testPadId+"&html="+ulHtml) + api.post(endPoint('setHTML')) + .send({ + "padID": testPadId, + "html": ulHtml, + }) .expect(function(res){ if(res.body.code !== 0) throw new Error("List HTML cant be imported") }) @@ -567,11 +631,44 @@ describe('getHTML', function(){ }); }) +describe('setHTML', function(){ + it('Sets the HTML of a Pad with white space between list items', function(done) { + api.get(endPoint('setHTML')+"&padID="+testPadId+"&html="+ulSpaceHtml) + .expect(function(res){ + if(res.body.code !== 0) throw new Error("List HTML cant be imported") + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('getHTML', function(){ + it('Gets back the HTML of a Pad with complex nested lists of different types', function(done) { + api.get(endPoint('getHTML')+"&padID="+testPadId) + .expect(function(res){ + var receivedHtml = res.body.data.html.replace("
        ", "").toLowerCase(); + if (receivedHtml !== expectedSpaceHtml) { + throw new Error(`HTML received from export is not the one we were expecting. + Received: + ${receivedHtml} + + Expected: + ${expectedSpaceHtml} + + Which is a slightly modified version of the originally imported one: + ${ulSpaceHtml}`); + } + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + describe('createPad', function(){ it('errors if pad can be created', function(done) { var badUrlChars = ["/", "%23", "%3F", "%26"]; async.map( - badUrlChars, + badUrlChars, function (badUrlChar, cb) { api.get(endPoint('createPad')+"&padID="+badUrlChar) .expect(function(res){ diff --git a/tests/backend/specs/api/sessionsAndGroups.js b/tests/backend/specs/api/sessionsAndGroups.js index 961cb2df9..cbc6e0a1a 100644 --- a/tests/backend/specs/api/sessionsAndGroups.js +++ b/tests/backend/specs/api/sessionsAndGroups.js @@ -1,7 +1,7 @@ var assert = require('assert') supertest = require(__dirname+'/../../../../src/node_modules/supertest'), fs = require('fs'), - settings = require(__dirname+'/../../loadSettings').loadSettings(), + settings = require(__dirname + '/../../../../src/node/utils/Settings'), api = supertest('http://'+settings.ip+":"+settings.port), path = require('path'); diff --git a/tests/backend/specs/api/test.doc b/tests/backend/specs/api/test.doc new file mode 100644 index 000000000..c92d46f2e Binary files /dev/null and b/tests/backend/specs/api/test.doc differ diff --git a/tests/backend/specs/api/test.docx b/tests/backend/specs/api/test.docx new file mode 100644 index 000000000..422bb4f03 Binary files /dev/null and b/tests/backend/specs/api/test.docx differ diff --git a/tests/backend/specs/api/test.etherpad b/tests/backend/specs/api/test.etherpad new file mode 100644 index 000000000..1c3121949 --- /dev/null +++ b/tests/backend/specs/api/test.etherpad @@ -0,0 +1 @@ +{"pad:Pd4b1Kgvv9qHZZtj8yzl":{"atext":{"text":"*hello\n","attribs":"*0*1*5*3*4+1*0+5|1+1"},"pool":{"numToAttrib":{"0":["author","a.ElbBWNTxmtRrfFqn"],"1":["insertorder","first"],"2":["list","bullet1"],"3":["lmkr","1"],"4":["start","1"],"5":["list","bullet2"]},"nextNum":6},"head":5,"chatHead":-1,"publicStatus":false,"passwordHash":null,"savedRevisions":[]},"globalAuthor:a.ElbBWNTxmtRrfFqn":{"colorId":50,"name":null,"timestamp":1595111151414,"padIDs":"Pd4b1Kgvv9qHZZtj8yzl"},"pad:Pd4b1Kgvv9qHZZtj8yzl:revs:0":{"changeset":"Z:1>bj|7+bj$Welcome to Etherpad!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nGet involved with Etherpad at https://etherpad.org\n\nWarning: DirtyDB is used. This is fine for testing but not recommended for production. -- To suppress these warning messages change suppressErrorsInPadText to true in your settings.json\n","meta":{"author":"","timestamp":1595111092400,"pool":{"numToAttrib":{},"attribToNum":{},"nextNum":0},"atext":{"text":"Welcome to Etherpad!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nGet involved with Etherpad at https://etherpad.org\n\nWarning: DirtyDB is used. This is fine for testing but not recommended for production. -- To suppress these warning messages change suppressErrorsInPadText to true in your settings.json\n\n","attribs":"|8+bk"}}},"pad:Pd4b1Kgvv9qHZZtj8yzl:revs:1":{"changeset":"Z:bk1*0*1*2*3*4+1$*","meta":{"author":"a.ElbBWNTxmtRrfFqn","timestamp":1595111119434}},"pad:Pd4b1Kgvv9qHZZtj8yzl:revs:3":{"changeset":"Z:2>0*5*4=1$","meta":{"author":"a.ElbBWNTxmtRrfFqn","timestamp":1595111127471}},"pad:Pd4b1Kgvv9qHZZtj8yzl:revs:4":{"changeset":"Z:2>2=1*0+2$he","meta":{"author":"a.ElbBWNTxmtRrfFqn","timestamp":1595111128230}},"pad:Pd4b1Kgvv9qHZZtj8yzl:revs:5":{"changeset":"Z:4>3=3*0+3$llo","meta":{"author":"a.ElbBWNTxmtRrfFqn","timestamp":1595111128727}}} \ No newline at end of file diff --git a/tests/backend/specs/api/test.odt b/tests/backend/specs/api/test.odt new file mode 100644 index 000000000..8ab62a612 Binary files /dev/null and b/tests/backend/specs/api/test.odt differ diff --git a/tests/backend/specs/api/test.pdf b/tests/backend/specs/api/test.pdf new file mode 100644 index 000000000..774c2ea70 Binary files /dev/null and b/tests/backend/specs/api/test.pdf differ diff --git a/tests/backend/specs/api/test.txt b/tests/backend/specs/api/test.txt new file mode 100644 index 000000000..b6e75c4cb --- /dev/null +++ b/tests/backend/specs/api/test.txt @@ -0,0 +1 @@ +This is the contents we insert into pads when testing the import functions. Thanks for using Etherpad! diff --git a/tests/backend/specs/api/tidy.js b/tests/backend/specs/api/tidy.js index 47cb49f62..3ef61931b 100644 --- a/tests/backend/specs/api/tidy.js +++ b/tests/backend/specs/api/tidy.js @@ -1,10 +1,12 @@ var assert = require('assert') + os = require('os'), fs = require('fs'), path = require('path'), TidyHtml = null, Settings = null; var npm = require("../../../../src/node_modules/npm/lib/npm.js"); +var nodeify = require('../../../../src/node_modules/nodeify'); describe('tidyHtml', function() { before(function(done) { @@ -16,6 +18,10 @@ describe('tidyHtml', function() { }); }); + function tidy(file, callback) { + return nodeify(TidyHtml.tidy(file), callback); + } + it('Tidies HTML', function(done) { // If the user hasn't configured Tidy, we skip this tests as it's required for this test if (!Settings.tidyHtml) { @@ -23,10 +29,11 @@ describe('tidyHtml', function() { } // Try to tidy up a bad HTML file - var tmpDir = process.env.TEMP || "/tmp"; + const tmpDir = os.tmpdir(); + var tmpFile = path.join(tmpDir, 'tmp_' + (Math.floor(Math.random() * 1000000)) + '.html') fs.writeFileSync(tmpFile, '

        a paragraph

      • List without outer UL
      • trailing closing p

        '); - TidyHtml.tidy(tmpFile, function(err){ + tidy(tmpFile, function(err){ assert.ok(!err); // Read the file again @@ -55,7 +62,7 @@ describe('tidyHtml', function() { this.skip(); } - TidyHtml.tidy('/some/none/existing/file.html', function(err) { + tidy('/some/none/existing/file.html', function(err) { assert.ok(err); return done(); }); diff --git a/tests/backend/specs/contentcollector.js b/tests/backend/specs/contentcollector.js new file mode 100644 index 000000000..7a92c69b1 --- /dev/null +++ b/tests/backend/specs/contentcollector.js @@ -0,0 +1,175 @@ +const Changeset = require("../../../src/static/js/Changeset"); +const contentcollector = require("../../../src/static/js/contentcollector"); +const AttributePool = require("../../../src/static/js/AttributePool"); +const cheerio = require("../../../src/node_modules/cheerio"); +const util = require('util'); + +const tests = { + nestedLi:{ + description: "Complex nested Li", + html: '
        1. one
          1. 1.1
        2. two
        ', + expectedLineAttribs : [ + '*0*1*2*3+1+3', '*0*4*2*5+1+3', '*0*1*2*5+1+3' + ], + expectedText: [ + '*one', '*1.1', '*two' + ] + }, + complexNest:{ + description: "Complex list of different types", + html: '
        • one
        • two
        • 0
        • 1
        • 2
          • 3
          • 4
        1. item
          1. item1
          2. item2
        ', + expectedLineAttribs : [ + '*0*1*2+1+3', + '*0*1*2+1+3', + '*0*1*2+1+1', + '*0*1*2+1+1', + '*0*1*2+1+1', + '*0*3*2+1+1', + '*0*3*2+1+1', + '*0*4*2*5+1+4', + '*0*6*2*7+1+5', + '*0*6*2*7+1+5' + ], + expectedText: [ + '*one', '*two', + '*0', '*1', + '*2', '*3', + '*4', '*item', + '*item1', '*item2' + ] + }, + ul: { + description : "Tests if uls properly get attributes", + html : "
        • a
        • b
        div

        foo

        ", + expectedLineAttribs : [ '*0*1*2+1+1', '*0*1*2+1+1', '+3' , '+3'], + expectedText: ["*a","*b", "div", "foo"] + } + , + ulIndented: { + description : "Tests if indented uls properly get attributes", + html : "
        • a
          • b
        • a

        foo

        ", + expectedLineAttribs : [ '*0*1*2+1+1', '*0*3*2+1+1', '*0*1*2+1+1', '+3' ], + expectedText: ["*a","*b", "*a", "foo"] + }, + ol: { + description : "Tests if ols properly get line numbers when in a normal OL", + html : "
        1. a
        2. b
        3. c

        test

        ", + expectedLineAttribs : ['*0*1*2*3+1+1', '*0*1*2*3+1+1', '*0*1*2*3+1+1', '+4'], + expectedText: ["*a","*b","*c", "test"], + noteToSelf: "Ensure empty P does not induce line attribute marker, wont this break the editor?" + } + , + lineDoBreakInOl:{ + description : "A single completely empty line break within an ol should reset count if OL is closed off..", + html : "
        1. should be 1

        hello

        1. should be 1
        2. should be 2

        ", + expectedLineAttribs : [ '*0*1*2*3+1+b', '+5', '*0*1*2*4+1+b', '*0*1*2*4+1+b', '' ], + expectedText: ["*should be 1","hello", "*should be 1","*should be 2", ""], + noteToSelf: "Shouldn't include attribute marker in the

        line" + }, + bulletListInOL:{ + description : "A bullet within an OL should not change numbering..", + html : "

        1. should be 1
          • should be a bullet
        2. should be 2

        ", + expectedLineAttribs : [ '*0*1*2*3+1+b', '*0*4*2*3+1+i', '*0*1*2*5+1+b', '' ], + expectedText: ["*should be 1","*should be a bullet","*should be 2", ""] + }, + testP:{ + description : "A single

        should create a new line", + html : "

        ", + expectedLineAttribs : [ '', ''], + expectedText: ["", ""], + noteToSelf: "

        should create a line break but not break numbering" + }, + nestedOl: { + description : "Tests if ols properly get line numbers when in a normal OL", + html : "a
        1. b
          1. c
        notlist

        foo

        ", + expectedLineAttribs : [ '+1', '*0*1*2*3+1+1', '*0*4*2*5+1+1', '+7', '+3' ], + expectedText: ["a","*b","*c", "notlist", "foo"], + noteToSelf: "Ensure empty P does not induce line attribute marker, wont this break the editor?" + }, + nestedOl: { + description : "First item being an UL then subsequent being OL will fail", + html : "
        • a
          1. b
          2. c
        ", + expectedLineAttribs : [ '+1', '*0*1*2*3+1+1', '*0*4*2*5+1+1' ], + expectedText: ["a","*b","*c"], + noteToSelf: "Ensure empty P does not induce line attribute marker, wont this break the editor?", + disabled: true + }, + lineDontBreakOL:{ + description : "A single completely empty line break within an ol should NOT reset count", + html : "
        1. should be 1
        2. should be 2
        3. should be 3

        ", + expectedLineAttribs : [ ], + expectedText: ["*should be 1","*should be 2","*should be 3"], + noteToSelf: "

        should create a line break but not break numbering -- This is what I can't get working!", + disabled: true + } + +} + +// For each test.. +for (let test in tests){ + let testObj = tests[test]; + + describe(test, function() { + if(testObj.disabled){ + return xit("DISABLED:", test, function(done){ + done(); + }) + } + + it(testObj.description, function(done) { + var $ = cheerio.load(testObj.html); // Load HTML into Cheerio + var doc = $('html')[0]; // Creates a dom-like representation of HTML + // Create an empty attribute pool + var apool = new AttributePool(); + // Convert a dom tree into a list of lines and attribute liens + // using the content collector object + var cc = contentcollector.makeContentCollector(true, null, apool); + cc.collectContent(doc); + var result = cc.finish(); + var recievedAttributes = result.lineAttribs; + var expectedAttributes = testObj.expectedLineAttribs; + var recievedText = new Array(result.lines) + var expectedText = testObj.expectedText; + + // Check recieved text matches the expected text + if(arraysEqual(recievedText[0], expectedText)){ + // console.log("PASS: Recieved Text did match Expected Text\nRecieved:", recievedText[0], "\nExpected:", testObj.expectedText) + }else{ + console.error("FAIL: Recieved Text did not match Expected Text\nRecieved:", recievedText[0], "\nExpected:", testObj.expectedText) + throw new Error(); + } + + // Check recieved attributes matches the expected attributes + if(arraysEqual(recievedAttributes, expectedAttributes)){ + // console.log("PASS: Recieved Attributes matched Expected Attributes"); + done(); + }else{ + console.error("FAIL", test, testObj.description); + console.error("FAIL: Recieved Attributes did not match Expected Attributes\nRecieved: ", recievedAttributes, "\nExpected: ", expectedAttributes) + console.error("FAILING HTML", testObj.html); + throw new Error(); + } + }); + + }); + +}; + + + + +function arraysEqual(a, b) { + if (a === b) return true; + if (a == null || b == null) return false; + if (a.length != b.length) return false; + + // If you don't care about the order of the elements inside + // the array, you should sort both arrays here. + // Please note that calling sort on an array will modify that array. + // you might want to clone your array first. + + for (var i = 0; i < a.length; ++i) { + if (a[i] !== b[i]) return false; + } + return true; +} diff --git a/tests/backend/specs/minify.js b/tests/backend/specs/minify.js new file mode 100644 index 000000000..4476fc7ad --- /dev/null +++ b/tests/backend/specs/minify.js @@ -0,0 +1,87 @@ + +const request = require(__dirname+'/../../../src/node_modules/supertest') + +/** + * types + * woff + * WASM + * html + * woff2 + * javascript + * + * require-js stuff + * + * try to require every single file in src/ + */ + +/** + * test header + * test header with garbage + * if-modified-since + * last-modfied + * + * + * + */ + + + +// ensure minification is true +describe('Minify', function(done) { + it('serves fonts', function(done) { + request('http://localhost:9001') + .get('/p/MINIFY_TEST_123') + //.set('Accept', 'application/json') + //.expect('Content-Type', /json/) + .expect(200, done); + }); +}); +//const assert = require('assert'); +//const supertest = require(__dirname+'/../../../../src/node_modules/supertest'); +//const fs = require('fs'); +//const settings = require(__dirname+'/../../../../src/node/utils/Settings'); +//const host = 'http://127.0.0.1:'+settings.port; +//const api = supertest('http://'+settings.ip+":"+settings.port); +//const path = require('path'); +//const async = require(__dirname+'/../../../../src/node_modules/async'); +//const request = require(__dirname+'/../../../../src/node_modules/request'); +//var filePath = path.join(__dirname, '../../../../APIKEY.txt'); +// +//var apiKey = fs.readFileSync(filePath, {encoding: 'utf-8'}); +//apiKey = apiKey.replace(/\n$/, ""); +//var apiVersion = 1; +//var testPadId = makeid(); +//var lastEdited = ""; +//var text = generateLongText(); +// +//describe('Connectivity', function(){ +// it('can connect', function(done) { +// api.get('/api/') +// .expect('Content-Type', /json/) +// .expect(200, done) +// }); +//}) +// +//describe('API Versioning', function(){ +// it('finds the version tag', function(done) { +// api.get('/api/') +// .expect(function(res){ +// apiVersion = res.body.currentVersion; +// if (!res.body.currentVersion) throw new Error("No version set in API"); +// return; +// }) +// .expect(200, done) +// }); +//}) +// +//function makeid() +//{ +// var text = ""; +// var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; +// +// for( var i=0; i < 5; i++ ){ +// text += possible.charAt(Math.floor(Math.random() * possible.length)); +// } +// return text; +//} +// diff --git a/tests/container/loadSettings.js b/tests/container/loadSettings.js new file mode 100644 index 000000000..4a1c46021 --- /dev/null +++ b/tests/container/loadSettings.js @@ -0,0 +1,37 @@ +/* + * ACHTUNG: this file is a hack used to load "settings.json.docker" instead of + * "settings.json", since in its present form the Settings module does + * not allow it. + * This is a remnant of an analogous file that was placed in + * /tests/backend/loadSettings.js + * + * TODO: modify the Settings module: + * 1) no side effects on module load + * 2) write a factory method that loads a configuration file (taking the + * file name from the command line, a function argument, or falling + * back to a default) + */ + +var jsonminify = require(__dirname+"/../../src/node_modules/jsonminify"); +const fs = require('fs'); + +function loadSettings(){ + var settingsStr = fs.readFileSync(__dirname+"/../../settings.json.docker").toString(); + // try to parse the settings + try { + if(settingsStr) { + settingsStr = jsonminify(settingsStr).replace(",]","]").replace(",}","}"); + var settings = JSON.parse(settingsStr); + + // custom settings for running in a container + settings.ip = 'localhost'; + settings.port = '9001'; + + return settings; + } + }catch(e){ + console.error("whoops something is bad with settings"); + } +} + +exports.loadSettings = loadSettings; diff --git a/tests/container/specs/api/pad.js b/tests/container/specs/api/pad.js new file mode 100644 index 000000000..f50b79864 --- /dev/null +++ b/tests/container/specs/api/pad.js @@ -0,0 +1,38 @@ +/* + * ACHTUNG: this file was copied & modified from the analogous + * /tests/backend/specs/api/pad.js + * + * TODO: unify those two files, and merge in a single one. + */ + +const supertest = require(__dirname+'/../../../../src/node_modules/supertest'); +const settings = require(__dirname+'/../../loadSettings').loadSettings(); +const api = supertest('http://'+settings.ip+":"+settings.port); + +var apiVersion = 1; + +describe('Connectivity', function(){ + it('can connect', function(done) { + api.get('/api/') + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('API Versioning', function(){ + it('finds the version tag', function(done) { + api.get('/api/') + .expect(function(res){ + if (!res.body.currentVersion) throw new Error("No version set in API"); + return; + }) + .expect(200, done) + }); +}) + +describe('Permission', function(){ + it('errors with invalid APIKey', function(done) { + api.get('/api/'+apiVersion+'/createPad?apikey=wrong_password&padID=test') + .expect(401, done) + }); +}) diff --git a/tests/frontend/helper.js b/tests/frontend/helper.js index 7a8d19b60..e14169bb1 100644 --- a/tests/frontend/helper.js +++ b/tests/frontend/helper.js @@ -1,11 +1,9 @@ var helper = {}; (function(){ - var $iframeContainer, $iframe, jsLibraries = {}; + var $iframe, jsLibraries = {}; helper.init = function(cb){ - $iframeContainer = $("#iframe-container"); - $.get('/static/js/jquery.js').done(function(code){ // make sure we don't override existing jquery jsLibraries["jquery"] = "if(typeof $ === 'undefined') {\n" + code + "\n}"; @@ -52,10 +50,49 @@ var helper = {}; return win.$; } - helper.clearCookies = function(){ - window.document.cookie = ""; + helper.clearSessionCookies = function(){ + // Expire cookies, so author and language are changed after reloading the pad. + // See https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie#Example_4_Reset_the_previous_cookie + window.document.cookie = 'token=;expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/'; + window.document.cookie = 'language=;expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/'; } + // Can only happen when the iframe exists, so we're doing it separately from other cookies + helper.clearPadPrefCookie = function(){ + helper.padChrome$.document.cookie = 'prefsHttp=;expires=Thu, 01 Jan 1970 00:00:00 GMT'; + } + + // Overwrite all prefs in pad cookie. Assumes http, not https. + // + // `helper.padChrome$.document.cookie` (the iframe) and `window.document.cookie` + // seem to have independent cookies, UNLESS we put path=/ here (which we don't). + // I don't fully understand it, but this function seems to properly simulate + // padCookie.setPref in the client code + helper.setPadPrefCookie = function(prefs){ + helper.padChrome$.document.cookie = ("prefsHttp=" + escape(JSON.stringify(prefs)) + ";expires=Thu, 01 Jan 3000 00:00:00 GMT"); + } + + // Functionality for knowing what key event type is required for tests + var evtType = "keydown"; + // if it's IE require keypress + if(window.navigator.userAgent.indexOf("MSIE") > -1){ + evtType = "keypress"; + } + // Edge also requires keypress. + if(window.navigator.userAgent.indexOf("Edge") > -1){ + evtType = "keypress"; + } + // Opera also requires keypress. + if(window.navigator.userAgent.indexOf("OPR") > -1){ + evtType = "keypress"; + } + helper.evtType = evtType; + + // @todo needs fixing asap + // newPad occasionally timeouts, might be a problem with ready/onload code during page setup + // This ensures that tests run regardless of this problem + helper.retry = 0 + helper.newPad = function(cb, padName){ //build opts object var opts = {clearCookies: true} @@ -65,38 +102,56 @@ var helper = {}; opts = _.defaults(cb, opts); } + // if opts.params is set we manipulate the URL to include URL parameters IE ?foo=Bah. + if(opts.params){ + var encodedParams = "?" + $.param(opts.params); + } + //clear cookies if(opts.clearCookies){ - helper.clearCookies(); + helper.clearSessionCookies(); } if(!padName) padName = "FRONTEND_TEST_" + helper.randomString(20); - $iframe = $(""); + $iframe = $(""); + + // needed for retry + let origPadName = padName; //clean up inner iframe references helper.padChrome$ = helper.padOuter$ = helper.padInner$ = null; - //clean up iframes properly to prevent IE from memoryleaking - $iframeContainer.find("iframe").purgeFrame().done(function(){ - $iframeContainer.append($iframe); - $iframe.one('load', function(){ - helper.waitFor(function(){ - return !$iframe.contents().find("#editorloadingbox").is(":visible"); - }, 50000).done(function(){ - helper.padChrome$ = getFrameJQuery( $('#iframe-container iframe')); - helper.padOuter$ = getFrameJQuery(helper.padChrome$('iframe[name="ace_outer"]')); - helper.padInner$ = getFrameJQuery( helper.padOuter$('iframe[name="ace_inner"]')); + //remove old iframe + $("#iframe-container iframe").remove(); + //set new iframe + $("#iframe-container").append($iframe); + $iframe.one('load', function(){ + helper.padChrome$ = getFrameJQuery($('#iframe-container iframe')); + if (opts.clearCookies) { + helper.clearPadPrefCookie(); + } + if (opts.padPrefs) { + helper.setPadPrefCookie(opts.padPrefs); + } + helper.waitFor(function(){ + return !$iframe.contents().find("#editorloadingbox").is(":visible"); + }, 10000).done(function(){ + helper.padOuter$ = getFrameJQuery(helper.padChrome$('iframe[name="ace_outer"]')); + helper.padInner$ = getFrameJQuery( helper.padOuter$('iframe[name="ace_inner"]')); - //disable all animations, this makes tests faster and easier - helper.padChrome$.fx.off = true; - helper.padOuter$.fx.off = true; - helper.padInner$.fx.off = true; + //disable all animations, this makes tests faster and easier + helper.padChrome$.fx.off = true; + helper.padOuter$.fx.off = true; + helper.padInner$.fx.off = true; - opts.cb(); - }).fail(function(){ + opts.cb(); + }).fail(function(){ + if (helper.retry > 3) { throw new Error("Pad never loaded"); - }); + } + helper.retry++; + helper.newPad(cb,origPadName); }); }); @@ -104,7 +159,7 @@ var helper = {}; } helper.waitFor = function(conditionFunc, _timeoutTime, _intervalTime){ - var timeoutTime = _timeoutTime || 1000; + var timeoutTime = _timeoutTime || 1900; var intervalTime = _intervalTime || 10; var deferred = $.Deferred(); @@ -216,4 +271,4 @@ var helper = {}; _it(name, func); } -})() \ No newline at end of file +})() diff --git a/tests/frontend/index.html b/tests/frontend/index.html index 1bf104952..d828e851c 100644 --- a/tests/frontend/index.html +++ b/tests/frontend/index.html @@ -14,11 +14,10 @@ - + - diff --git a/tests/frontend/lib/expect.js b/tests/frontend/lib/expect.js index ab5a1eea3..c647cf2be 100644 --- a/tests/frontend/lib/expect.js +++ b/tests/frontend/lib/expect.js @@ -65,7 +65,7 @@ var name = $flags[i] , assertion = new Assertion(this.obj, name, this) - + if ('function' == typeof Assertion.prototype[name]) { // clone the function, make sure we dont touch the prot reference var old = this[name]; @@ -148,7 +148,7 @@ if ('object' == typeof fn && not) { // in the presence of a matcher, ensure the `not` only applies to // the matching. - this.flags.not = false; + this.flags.not = false; } var name = this.obj.name || 'fn'; @@ -219,7 +219,7 @@ }; /** - * Assert within start to finish (inclusive). + * Assert within start to finish (inclusive). * * @param {Number} start * @param {Number} finish @@ -298,7 +298,7 @@ , function(){ return 'expected ' + i(this.obj) + ' to be above ' + n }); return this; }; - + /** * Assert string value matches _regexp_. * @@ -359,13 +359,13 @@ } catch (e) { hasProp = undefined !== this.obj[name] } - + this.assert( hasProp , function(){ return 'expected ' + i(this.obj) + ' to have a property ' + i(name) } , function(){ return 'expected ' + i(this.obj) + ' to not have a property ' + i(name) }); } - + if (undefined !== val) { this.assert( val === this.obj[name] @@ -537,7 +537,7 @@ return html; } }; - + // Returns true if object is a DOM element. var isDOMElement = function (object) { if (typeof HTMLElement === 'object') { @@ -843,9 +843,9 @@ expect.eql = function eql (actual, expected) { // 7.1. All identical values are equivalent, as determined by ===. - if (actual === expected) { + if (actual === expected) { return true; - } else if ('undefined' != typeof Buffer + } else if ('undefined' != typeof Buffer && Buffer.isBuffer(actual) && Buffer.isBuffer(expected)) { if (actual.length != expected.length) return false; diff --git a/tests/frontend/lib/jquery.iframe.js b/tests/frontend/lib/jquery.iframe.js deleted file mode 100644 index 604ae1bc2..000000000 --- a/tests/frontend/lib/jquery.iframe.js +++ /dev/null @@ -1,40 +0,0 @@ -//copied from http://stackoverflow.com/questions/8407946/is-it-possible-to-use-iframes-in-ie-without-memory-leaks -(function($) { - $.fn.purgeFrame = function() { - var deferred; - var browser = bowser; - - if (browser.msie && parseFloat(browser.version, 10) < 9) { - deferred = purge(this); - } else { - this.remove(); - deferred = $.Deferred(); - deferred.resolve(); - } - - return deferred; - }; - - function purge($frame) { - var sem = $frame.length - , deferred = $.Deferred(); - - $frame.load(function() { - var frame = this; - frame.contentWindow.document.innerHTML = ''; - - sem -= 1; - if (sem <= 0) { - $frame.remove(); - deferred.resolve(); - } - }); - $frame.attr('src', 'about:blank'); - - if ($frame.length === 0) { - deferred.resolve(); - } - - return deferred.promise(); - } -})(jQuery); diff --git a/tests/frontend/lib/mocha.js b/tests/frontend/lib/mocha.js index 5f2da0139..a9669b1b7 100644 --- a/tests/frontend/lib/mocha.js +++ b/tests/frontend/lib/mocha.js @@ -1,256 +1,372 @@ -;(function(){ +(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i} promise determining if browser notification + * permissible when fulfilled. */ - -EventEmitter.prototype.removeListener = function (name, fn) { - if (this.$events && this.$events[name]) { - var list = this.$events[name]; - - if (isArray(list)) { - var pos = -1; - - for (var i = 0, l = list.length; i < l; i++) { - if (list[i] === fn || (list[i].listener && list[i].listener === fn)) { - pos = i; - break; - } - } - - if (pos < 0) { - return this; - } - - list.splice(pos, 1); - - if (!list.length) { - delete this.$events[name]; - } - } else if (list === fn || (list.listener && list.listener === fn)) { - delete this.$events[name]; +function isPermitted() { + var permitted = { + granted: function allow() { + return Promise.resolve(true); + }, + denied: function deny() { + return Promise.resolve(false); + }, + default: function ask() { + return Notification.requestPermission().then(function(permission) { + return permission === 'granted'; + }); } - } + }; - return this; -}; + return permitted[Notification.permission](); +} /** - * Removes all listeners for an event. + * @summary + * Determines if notification should proceed. * - * @api public + * @description + * Notification shall not proceed unless `value` is true. + * + * `value` will equal one of: + *
          + *
        • true (from `isPermitted`)
        • + *
        • false (from `isPermitted`)
        • + *
        • undefined (from `Promise.race`)
        • + *
        + * + * @private + * @param {boolean|undefined} value - Determines if notification permissible. + * @returns {Promise} Notification can proceed */ - -EventEmitter.prototype.removeAllListeners = function (name) { - if (name === undefined) { - this.$events = {}; - return this; +function canNotify(value) { + if (!value) { + var why = value === false ? 'blocked' : 'unacknowledged'; + var reason = 'not permitted by user (' + why + ')'; + return Promise.reject(new Error(reason)); } - - if (this.$events && this.$events[name]) { - this.$events[name] = null; - } - - return this; -}; + return Promise.resolve(); +} /** - * Gets all listeners for a certain event. + * Displays the notification. * - * @api public + * @private + * @param {Runner} runner - Runner instance. */ +function display(runner) { + var stats = runner.stats; + var symbol = { + cross: '\u274C', + tick: '\u2705' + }; + var logo = require('../../package').notifyLogo; + var _message; + var message; + var title; -EventEmitter.prototype.listeners = function (name) { - if (!this.$events) { - this.$events = {}; - } - - if (!this.$events[name]) { - this.$events[name] = []; - } - - if (!isArray(this.$events[name])) { - this.$events[name] = [this.$events[name]]; - } - - return this.$events[name]; -}; - -/** - * Emits an event. - * - * @api public - */ - -EventEmitter.prototype.emit = function (name) { - if (!this.$events) { - return false; - } - - var handler = this.$events[name]; - - if (!handler) { - return false; - } - - var args = [].slice.call(arguments, 1); - - if ('function' == typeof handler) { - handler.apply(this, args); - } else if (isArray(handler)) { - var listeners = handler.slice(); - - for (var i = 0, l = listeners.length; i < l; i++) { - listeners[i].apply(this, args); - } + if (stats.failures) { + _message = stats.failures + ' of ' + stats.tests + ' tests failed'; + message = symbol.cross + ' ' + _message; + title = 'Failed'; } else { - return false; + _message = stats.passes + ' tests passed in ' + stats.duration + 'ms'; + message = symbol.tick + ' ' + _message; + title = 'Passed'; } - return true; -}; -}); // module: browser/events.js + // Send notification + var options = { + badge: logo, + body: message, + dir: 'ltr', + icon: logo, + lang: 'en-US', + name: 'mocha', + requireInteraction: false, + timestamp: Date.now() + }; + var notification = new Notification(title, options); -require.register("browser/fs.js", function(module, exports, require){ + // Autoclose after brief delay (makes various browsers act same) + var FORCE_DURATION = 4000; + setTimeout(notification.close.bind(notification), FORCE_DURATION); +} -}); // module: browser/fs.js +/** + * As notifications are tangential to our purpose, just log the error. + * + * @private + * @param {Error} err - Why notification didn't happen. + */ +function notPermitted(err) { + console.error('notification error:', err.message); +} -require.register("browser/path.js", function(module, exports, require){ - -}); // module: browser/path.js - -require.register("browser/progress.js", function(module, exports, require){ +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"../../package":90,"../runner":34,"_process":69}],3:[function(require,module,exports){ +'use strict'; /** * Expose `Progress`. @@ -261,7 +377,6 @@ module.exports = Progress; /** * Initialize a new `Progress` indicator. */ - function Progress() { this.percent = 0; this.size(0); @@ -270,52 +385,48 @@ function Progress() { } /** - * Set progress size to `n`. + * Set progress size to `size`. * - * @param {Number} n - * @return {Progress} for chaining - * @api public + * @public + * @param {number} size + * @return {Progress} Progress instance. */ - -Progress.prototype.size = function(n){ - this._size = n; +Progress.prototype.size = function(size) { + this._size = size; return this; }; /** - * Set text to `str`. + * Set text to `text`. * - * @param {String} str - * @return {Progress} for chaining - * @api public + * @public + * @param {string} text + * @return {Progress} Progress instance. */ - -Progress.prototype.text = function(str){ - this._text = str; +Progress.prototype.text = function(text) { + this._text = text; return this; }; /** - * Set font size to `n`. + * Set font size to `size`. * - * @param {Number} n - * @return {Progress} for chaining - * @api public + * @public + * @param {number} size + * @return {Progress} Progress instance. */ - -Progress.prototype.fontSize = function(n){ - this._fontSize = n; +Progress.prototype.fontSize = function(size) { + this._fontSize = size; return this; }; /** - * Set font `family`. + * Set font to `family`. * - * @param {String} family - * @return {Progress} for chaining + * @param {string} family + * @return {Progress} Progress instance. */ - -Progress.prototype.font = function(family){ +Progress.prototype.font = function(family) { this._font = family; return this; }; @@ -323,11 +434,10 @@ Progress.prototype.font = function(family){ /** * Update percentage to `n`. * - * @param {Number} n - * @return {Progress} for chaining + * @param {number} n + * @return {Progress} Progress instance. */ - -Progress.prototype.update = function(n){ +Progress.prototype.update = function(n) { this.percent = n; return this; }; @@ -336,62 +446,68 @@ Progress.prototype.update = function(n){ * Draw on `ctx`. * * @param {CanvasRenderingContext2d} ctx - * @return {Progress} for chaining + * @return {Progress} Progress instance. */ +Progress.prototype.draw = function(ctx) { + try { + var percent = Math.min(this.percent, 100); + var size = this._size; + var half = size / 2; + var x = half; + var y = half; + var rad = half - 1; + var fontSize = this._fontSize; -Progress.prototype.draw = function(ctx){ - var percent = Math.min(this.percent, 100) - , size = this._size - , half = size / 2 - , x = half - , y = half - , rad = half - 1 - , fontSize = this._fontSize; + ctx.font = fontSize + 'px ' + this._font; - ctx.font = fontSize + 'px ' + this._font; + var angle = Math.PI * 2 * (percent / 100); + ctx.clearRect(0, 0, size, size); - var angle = Math.PI * 2 * (percent / 100); - ctx.clearRect(0, 0, size, size); + // outer circle + ctx.strokeStyle = '#9f9f9f'; + ctx.beginPath(); + ctx.arc(x, y, rad, 0, angle, false); + ctx.stroke(); - // outer circle - ctx.strokeStyle = '#9f9f9f'; - ctx.beginPath(); - ctx.arc(x, y, rad, 0, angle, false); - ctx.stroke(); + // inner circle + ctx.strokeStyle = '#eee'; + ctx.beginPath(); + ctx.arc(x, y, rad - 1, 0, angle, true); + ctx.stroke(); - // inner circle - ctx.strokeStyle = '#eee'; - ctx.beginPath(); - ctx.arc(x, y, rad - 1, 0, angle, true); - ctx.stroke(); - - // text - var text = this._text || (percent | 0) + '%' - , w = ctx.measureText(text).width; - - ctx.fillText( - text - , x - w / 2 + 1 - , y + fontSize / 2 - 1); + // text + var text = this._text || (percent | 0) + '%'; + var w = ctx.measureText(text).width; + ctx.fillText(text, x - w / 2 + 1, y + fontSize / 2 - 1); + } catch (ignore) { + // don't fail if we can't render progress + } return this; }; -}); // module: browser/progress.js +},{}],4:[function(require,module,exports){ +(function (global){ +'use strict'; -require.register("browser/tty.js", function(module, exports, require){ - -exports.isatty = function(){ +exports.isatty = function isatty() { return true; }; -exports.getWindowSize = function(){ - return [window.innerHeight, window.innerWidth]; +exports.getWindowSize = function getWindowSize() { + if ('innerHeight' in global) { + return [global.innerHeight, global.innerWidth]; + } + // In a Web Worker, the DOM Window is not available. + return [640, 480]; }; -}); // module: browser/tty.js - -require.register("context.js", function(module, exports, require){ +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],5:[function(require,module,exports){ +'use strict'; +/** + * @module Context + */ /** * Expose `Context`. */ @@ -401,75 +517,243 @@ module.exports = Context; /** * Initialize a new `Context`. * - * @api private + * @private */ - -function Context(){} +function Context() {} /** * Set or get the context `Runnable` to `runnable`. * + * @private * @param {Runnable} runnable - * @return {Context} - * @api private + * @return {Context} context */ - -Context.prototype.runnable = function(runnable){ - if (0 == arguments.length) return this._runnable; +Context.prototype.runnable = function(runnable) { + if (!arguments.length) { + return this._runnable; + } this.test = this._runnable = runnable; return this; }; /** - * Set test timeout `ms`. + * Set or get test timeout `ms`. * - * @param {Number} ms + * @private + * @param {number} ms * @return {Context} self - * @api private */ - -Context.prototype.timeout = function(ms){ +Context.prototype.timeout = function(ms) { + if (!arguments.length) { + return this.runnable().timeout(); + } this.runnable().timeout(ms); return this; }; /** - * Set test slowness threshold `ms`. + * Set test timeout `enabled`. * - * @param {Number} ms + * @private + * @param {boolean} enabled * @return {Context} self - * @api private */ +Context.prototype.enableTimeouts = function(enabled) { + if (!arguments.length) { + return this.runnable().enableTimeouts(); + } + this.runnable().enableTimeouts(enabled); + return this; +}; -Context.prototype.slow = function(ms){ +/** + * Set or get test slowness threshold `ms`. + * + * @private + * @param {number} ms + * @return {Context} self + */ +Context.prototype.slow = function(ms) { + if (!arguments.length) { + return this.runnable().slow(); + } this.runnable().slow(ms); return this; }; /** - * Inspect the context void of `._runnable`. + * Mark a test as skipped. * - * @return {String} - * @api private + * @private + * @throws Pending */ - -Context.prototype.inspect = function(){ - return JSON.stringify(this, function(key, val){ - if ('_runnable' == key) return; - if ('test' == key) return; - return val; - }, 2); +Context.prototype.skip = function() { + this.runnable().skip(); }; -}); // module: context.js - -require.register("hook.js", function(module, exports, require){ - /** - * Module dependencies. + * Set or get a number of allowed retries on failed tests + * + * @private + * @param {number} n + * @return {Context} self + */ +Context.prototype.retries = function(n) { + if (!arguments.length) { + return this.runnable().retries(); + } + this.runnable().retries(n); + return this; +}; + +},{}],6:[function(require,module,exports){ +'use strict'; +/** + * @module Errors + */ +/** + * Factory functions to create throwable error objects */ +/** + * Creates an error object to be thrown when no files to be tested could be found using specified pattern. + * + * @public + * @param {string} message - Error message to be displayed. + * @param {string} pattern - User-specified argument value. + * @returns {Error} instance detailing the error condition + */ +function createNoFilesMatchPatternError(message, pattern) { + var err = new Error(message); + err.code = 'ERR_MOCHA_NO_FILES_MATCH_PATTERN'; + err.pattern = pattern; + return err; +} + +/** + * Creates an error object to be thrown when the reporter specified in the options was not found. + * + * @public + * @param {string} message - Error message to be displayed. + * @param {string} reporter - User-specified reporter value. + * @returns {Error} instance detailing the error condition + */ +function createInvalidReporterError(message, reporter) { + var err = new TypeError(message); + err.code = 'ERR_MOCHA_INVALID_REPORTER'; + err.reporter = reporter; + return err; +} + +/** + * Creates an error object to be thrown when the interface specified in the options was not found. + * + * @public + * @param {string} message - Error message to be displayed. + * @param {string} ui - User-specified interface value. + * @returns {Error} instance detailing the error condition + */ +function createInvalidInterfaceError(message, ui) { + var err = new Error(message); + err.code = 'ERR_MOCHA_INVALID_INTERFACE'; + err.interface = ui; + return err; +} + +/** + * Creates an error object to be thrown when a behavior, option, or parameter is unsupported. + * + * @public + * @param {string} message - Error message to be displayed. + * @returns {Error} instance detailing the error condition + */ +function createUnsupportedError(message) { + var err = new Error(message); + err.code = 'ERR_MOCHA_UNSUPPORTED'; + return err; +} + +/** + * Creates an error object to be thrown when an argument is missing. + * + * @public + * @param {string} message - Error message to be displayed. + * @param {string} argument - Argument name. + * @param {string} expected - Expected argument datatype. + * @returns {Error} instance detailing the error condition + */ +function createMissingArgumentError(message, argument, expected) { + return createInvalidArgumentTypeError(message, argument, expected); +} + +/** + * Creates an error object to be thrown when an argument did not use the supported type + * + * @public + * @param {string} message - Error message to be displayed. + * @param {string} argument - Argument name. + * @param {string} expected - Expected argument datatype. + * @returns {Error} instance detailing the error condition + */ +function createInvalidArgumentTypeError(message, argument, expected) { + var err = new TypeError(message); + err.code = 'ERR_MOCHA_INVALID_ARG_TYPE'; + err.argument = argument; + err.expected = expected; + err.actual = typeof argument; + return err; +} + +/** + * Creates an error object to be thrown when an argument did not use the supported value + * + * @public + * @param {string} message - Error message to be displayed. + * @param {string} argument - Argument name. + * @param {string} value - Argument value. + * @param {string} [reason] - Why value is invalid. + * @returns {Error} instance detailing the error condition + */ +function createInvalidArgumentValueError(message, argument, value, reason) { + var err = new TypeError(message); + err.code = 'ERR_MOCHA_INVALID_ARG_VALUE'; + err.argument = argument; + err.value = value; + err.reason = typeof reason !== 'undefined' ? reason : 'is invalid'; + return err; +} + +/** + * Creates an error object to be thrown when an exception was caught, but the `Error` is falsy or undefined. + * + * @public + * @param {string} message - Error message to be displayed. + * @returns {Error} instance detailing the error condition + */ +function createInvalidExceptionError(message, value) { + var err = new Error(message); + err.code = 'ERR_MOCHA_INVALID_EXCEPTION'; + err.valueType = typeof value; + err.value = value; + return err; +} + +module.exports = { + createInvalidArgumentTypeError: createInvalidArgumentTypeError, + createInvalidArgumentValueError: createInvalidArgumentValueError, + createInvalidExceptionError: createInvalidExceptionError, + createInvalidInterfaceError: createInvalidInterfaceError, + createInvalidReporterError: createInvalidReporterError, + createMissingArgumentError: createMissingArgumentError, + createNoFilesMatchPatternError: createNoFilesMatchPatternError, + createUnsupportedError: createUnsupportedError +}; + +},{}],7:[function(require,module,exports){ +'use strict'; + var Runnable = require('./runnable'); +var inherits = require('./utils').inherits; /** * Expose `Hook`. @@ -478,13 +762,13 @@ var Runnable = require('./runnable'); module.exports = Hook; /** - * Initialize a new `Hook` with the given `title` and callback `fn`. + * Initialize a new `Hook` with the given `title` and callback `fn` * + * @class + * @extends Runnable * @param {String} title * @param {Function} fn - * @api private */ - function Hook(title, fn) { Runnable.call(this, title, fn); this.type = 'hook'; @@ -493,22 +777,19 @@ function Hook(title, fn) { /** * Inherit from `Runnable.prototype`. */ - -Hook.prototype = new Runnable; -Hook.prototype.constructor = Hook; - +inherits(Hook, Runnable); /** * Get or set the test `err`. * + * @memberof Hook + * @public * @param {Error} err * @return {Error} - * @api public */ - -Hook.prototype.error = function(err){ - if (0 == arguments.length) { - var err = this._error; +Hook.prototype.error = function(err) { + if (!arguments.length) { + err = this._error; this._error = null; return err; } @@ -516,107 +797,80 @@ Hook.prototype.error = function(err){ this._error = err; }; +},{"./runnable":33,"./utils":38}],8:[function(require,module,exports){ +'use strict'; -}); // module: hook.js - -require.register("interfaces/bdd.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Suite = require('../suite') - , Test = require('../test'); +var Test = require('../test'); +var EVENT_FILE_PRE_REQUIRE = require('../suite').constants + .EVENT_FILE_PRE_REQUIRE; /** * BDD-style interface: - * - * describe('Array', function(){ - * describe('#indexOf()', function(){ - * it('should return -1 when not present', function(){ * + * describe('Array', function() { + * describe('#indexOf()', function() { + * it('should return -1 when not present', function() { + * // ... * }); * - * it('should return the index when present', function(){ - * + * it('should return the index when present', function() { + * // ... * }); * }); * }); - * + * + * @param {Suite} suite Root suite. */ - -module.exports = function(suite){ +module.exports = function bddInterface(suite) { var suites = [suite]; - suite.on('pre-require', function(context, file, mocha){ - - /** - * Execute before running tests. - */ - - context.before = function(fn){ - suites[0].beforeAll(fn); - }; - - /** - * Execute after running tests. - */ - - context.after = function(fn){ - suites[0].afterAll(fn); - }; - - /** - * Execute before each test case. - */ - - context.beforeEach = function(fn){ - suites[0].beforeEach(fn); - }; - - /** - * Execute after each test case. - */ - - context.afterEach = function(fn){ - suites[0].afterEach(fn); - }; + suite.on(EVENT_FILE_PRE_REQUIRE, function(context, file, mocha) { + var common = require('./common')(suites, context, mocha); + context.before = common.before; + context.after = common.after; + context.beforeEach = common.beforeEach; + context.afterEach = common.afterEach; + context.run = mocha.options.delay && common.runWithSuite(suite); /** * Describe a "suite" with the given `title` * and callback `fn` containing nested suites * and/or tests. */ - - context.describe = context.context = function(title, fn){ - var suite = Suite.create(suites[0], title); - suites.unshift(suite); - fn(); - suites.shift(); - return suite; + + context.describe = context.context = function(title, fn) { + return common.suite.create({ + title: title, + file: file, + fn: fn + }); }; /** * Pending describe. */ - context.xdescribe = - context.xcontext = - context.describe.skip = function(title, fn){ - var suite = Suite.create(suites[0], title); - suite.pending = true; - suites.unshift(suite); - fn(); - suites.shift(); + context.xdescribe = context.xcontext = context.describe.skip = function( + title, + fn + ) { + return common.suite.skip({ + title: title, + file: file, + fn: fn + }); }; /** * Exclusive suite. */ - context.describe.only = function(title, fn){ - var suite = context.describe(title, fn); - mocha.grep(suite.fullTitle()); + context.describe.only = function(title, fn) { + return common.suite.only({ + title: title, + file: file, + fn: fn + }); }; /** @@ -625,10 +879,13 @@ module.exports = function(suite){ * acting as a thunk. */ - context.it = context.specify = function(title, fn){ + context.it = context.specify = function(title, fn) { var suite = suites[0]; - if (suite.pending) var fn = null; + if (suite.isPending()) { + fn = null; + } var test = new Test(title, fn); + test.file = file; suite.addTest(test); return test; }; @@ -637,60 +894,253 @@ module.exports = function(suite){ * Exclusive test-case. */ - context.it.only = function(title, fn){ - var test = context.it(title, fn); - mocha.grep(test.fullTitle()); + context.it.only = function(title, fn) { + return common.test.only(mocha, context.it(title, fn)); }; /** * Pending test case. */ - context.xit = - context.xspecify = - context.it.skip = function(title){ - context.it(title); + context.xit = context.xspecify = context.it.skip = function(title) { + return context.it(title); + }; + + /** + * Number of attempts to retry. + */ + context.it.retries = function(n) { + context.retries(n); }; }); }; -}); // module: interfaces/bdd.js +module.exports.description = 'BDD or RSpec style [default]'; -require.register("interfaces/exports.js", function(module, exports, require){ +},{"../suite":36,"../test":37,"./common":9}],9:[function(require,module,exports){ +'use strict'; + +var Suite = require('../suite'); +var errors = require('../errors'); +var createMissingArgumentError = errors.createMissingArgumentError; /** - * Module dependencies. + * Functions common to more than one interface. + * + * @param {Suite[]} suites + * @param {Context} context + * @param {Mocha} mocha + * @return {Object} An object containing common functions. */ +module.exports = function(suites, context, mocha) { + /** + * Check if the suite should be tested. + * + * @private + * @param {Suite} suite - suite to check + * @returns {boolean} + */ + function shouldBeTested(suite) { + return ( + !mocha.options.grep || + (mocha.options.grep && + mocha.options.grep.test(suite.fullTitle()) && + !mocha.options.invert) + ); + } -var Suite = require('../suite') - , Test = require('../test'); + return { + /** + * This is only present if flag --delay is passed into Mocha. It triggers + * root suite execution. + * + * @param {Suite} suite The root suite. + * @return {Function} A function which runs the root suite + */ + runWithSuite: function runWithSuite(suite) { + return function run() { + suite.run(); + }; + }, + + /** + * Execute before running tests. + * + * @param {string} name + * @param {Function} fn + */ + before: function(name, fn) { + suites[0].beforeAll(name, fn); + }, + + /** + * Execute after running tests. + * + * @param {string} name + * @param {Function} fn + */ + after: function(name, fn) { + suites[0].afterAll(name, fn); + }, + + /** + * Execute before each test case. + * + * @param {string} name + * @param {Function} fn + */ + beforeEach: function(name, fn) { + suites[0].beforeEach(name, fn); + }, + + /** + * Execute after each test case. + * + * @param {string} name + * @param {Function} fn + */ + afterEach: function(name, fn) { + suites[0].afterEach(name, fn); + }, + + suite: { + /** + * Create an exclusive Suite; convenience function + * See docstring for create() below. + * + * @param {Object} opts + * @returns {Suite} + */ + only: function only(opts) { + opts.isOnly = true; + return this.create(opts); + }, + + /** + * Create a Suite, but skip it; convenience function + * See docstring for create() below. + * + * @param {Object} opts + * @returns {Suite} + */ + skip: function skip(opts) { + opts.pending = true; + return this.create(opts); + }, + + /** + * Creates a suite. + * + * @param {Object} opts Options + * @param {string} opts.title Title of Suite + * @param {Function} [opts.fn] Suite Function (not always applicable) + * @param {boolean} [opts.pending] Is Suite pending? + * @param {string} [opts.file] Filepath where this Suite resides + * @param {boolean} [opts.isOnly] Is Suite exclusive? + * @returns {Suite} + */ + create: function create(opts) { + var suite = Suite.create(suites[0], opts.title); + suite.pending = Boolean(opts.pending); + suite.file = opts.file; + suites.unshift(suite); + if (opts.isOnly) { + if (mocha.options.forbidOnly && shouldBeTested(suite)) { + throw new Error('`.only` forbidden'); + } + + suite.parent.appendOnlySuite(suite); + } + if (suite.pending) { + if (mocha.options.forbidPending && shouldBeTested(suite)) { + throw new Error('Pending test forbidden'); + } + } + if (typeof opts.fn === 'function') { + opts.fn.call(suite); + suites.shift(); + } else if (typeof opts.fn === 'undefined' && !suite.pending) { + throw createMissingArgumentError( + 'Suite "' + + suite.fullTitle() + + '" was defined but no callback was supplied. ' + + 'Supply a callback or explicitly skip the suite.', + 'callback', + 'function' + ); + } else if (!opts.fn && suite.pending) { + suites.shift(); + } + + return suite; + } + }, + + test: { + /** + * Exclusive test-case. + * + * @param {Object} mocha + * @param {Function} test + * @returns {*} + */ + only: function(mocha, test) { + test.parent.appendOnlyTest(test); + return test; + }, + + /** + * Pending test case. + * + * @param {string} title + */ + skip: function(title) { + context.test(title); + }, + + /** + * Number of retry attempts + * + * @param {number} n + */ + retries: function(n) { + context.retries(n); + } + } + }; +}; + +},{"../errors":6,"../suite":36}],10:[function(require,module,exports){ +'use strict'; +var Suite = require('../suite'); +var Test = require('../test'); /** - * TDD-style interface: - * + * Exports-style (as Node.js module) interface: + * * exports.Array = { * '#indexOf()': { - * 'should return -1 when the value is not present': function(){ - * + * 'should return -1 when the value is not present': function() { + * * }, * - * 'should return the correct index when the value is present': function(){ - * + * 'should return the correct index when the value is present': function() { + * * } * } * }; - * + * + * @param {Suite} suite Root suite. */ - -module.exports = function(suite){ +module.exports = function(suite) { var suites = [suite]; - suite.on('require', visit); + suite.on(Suite.constants.EVENT_FILE_REQUIRE, visit); - function visit(obj) { + function visit(obj, file) { var suite; for (var key in obj) { - if ('function' == typeof obj[key]) { + if (typeof obj[key] === 'function') { var fn = obj[key]; switch (key) { case 'before': @@ -706,107 +1156,101 @@ module.exports = function(suite){ suites[0].afterEach(fn); break; default: - suites[0].addTest(new Test(key, fn)); + var test = new Test(key, fn); + test.file = file; + suites[0].addTest(test); } } else { - var suite = Suite.create(suites[0], key); + suite = Suite.create(suites[0], key); suites.unshift(suite); - visit(obj[key]); + visit(obj[key], file); suites.shift(); } } } }; -}); // module: interfaces/exports.js -require.register("interfaces/index.js", function(module, exports, require){ +module.exports.description = 'Node.js module ("exports") style'; + +},{"../suite":36,"../test":37}],11:[function(require,module,exports){ +'use strict'; exports.bdd = require('./bdd'); exports.tdd = require('./tdd'); exports.qunit = require('./qunit'); exports.exports = require('./exports'); -}); // module: interfaces/index.js +},{"./bdd":8,"./exports":10,"./qunit":12,"./tdd":13}],12:[function(require,module,exports){ +'use strict'; -require.register("interfaces/qunit.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Suite = require('../suite') - , Test = require('../test'); +var Test = require('../test'); +var EVENT_FILE_PRE_REQUIRE = require('../suite').constants + .EVENT_FILE_PRE_REQUIRE; /** * QUnit-style interface: - * + * * suite('Array'); - * - * test('#length', function(){ + * + * test('#length', function() { * var arr = [1,2,3]; * ok(arr.length == 3); * }); - * - * test('#indexOf()', function(){ + * + * test('#indexOf()', function() { * var arr = [1,2,3]; * ok(arr.indexOf(1) == 0); * ok(arr.indexOf(2) == 1); * ok(arr.indexOf(3) == 2); * }); - * + * * suite('String'); - * - * test('#length', function(){ + * + * test('#length', function() { * ok('foo'.length == 3); * }); - * + * + * @param {Suite} suite Root suite. */ - -module.exports = function(suite){ +module.exports = function qUnitInterface(suite) { var suites = [suite]; - suite.on('pre-require', function(context){ - - /** - * Execute before running tests. - */ - - context.before = function(fn){ - suites[0].beforeAll(fn); - }; - - /** - * Execute after running tests. - */ - - context.after = function(fn){ - suites[0].afterAll(fn); - }; - - /** - * Execute before each test case. - */ - - context.beforeEach = function(fn){ - suites[0].beforeEach(fn); - }; - - /** - * Execute after each test case. - */ - - context.afterEach = function(fn){ - suites[0].afterEach(fn); - }; + suite.on(EVENT_FILE_PRE_REQUIRE, function(context, file, mocha) { + var common = require('./common')(suites, context, mocha); + context.before = common.before; + context.after = common.after; + context.beforeEach = common.beforeEach; + context.afterEach = common.afterEach; + context.run = mocha.options.delay && common.runWithSuite(suite); /** * Describe a "suite" with the given `title`. */ - - context.suite = function(title){ - if (suites.length > 1) suites.shift(); - var suite = Suite.create(suites[0], title); - suites.unshift(suite); + + context.suite = function(title) { + if (suites.length > 1) { + suites.shift(); + } + return common.suite.create({ + title: title, + file: file, + fn: false + }); + }; + + /** + * Exclusive Suite. + */ + + context.suite.only = function(title) { + if (suites.length > 1) { + suites.shift(); + } + return common.suite.only({ + title: title, + file: file, + fn: false + }); }; /** @@ -815,116 +1259,9 @@ module.exports = function(suite){ * acting as a thunk. */ - context.test = function(title, fn){ - suites[0].addTest(new Test(title, fn)); - }; - }); -}; - -}); // module: interfaces/qunit.js - -require.register("interfaces/tdd.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Suite = require('../suite') - , Test = require('../test'); - -/** - * TDD-style interface: - * - * suite('Array', function(){ - * suite('#indexOf()', function(){ - * suiteSetup(function(){ - * - * }); - * - * test('should return -1 when not present', function(){ - * - * }); - * - * test('should return the index when present', function(){ - * - * }); - * - * suiteTeardown(function(){ - * - * }); - * }); - * }); - * - */ - -module.exports = function(suite){ - var suites = [suite]; - - suite.on('pre-require', function(context, file, mocha){ - - /** - * Execute before each test case. - */ - - context.setup = function(fn){ - suites[0].beforeEach(fn); - }; - - /** - * Execute after each test case. - */ - - context.teardown = function(fn){ - suites[0].afterEach(fn); - }; - - /** - * Execute before the suite. - */ - - context.suiteSetup = function(fn){ - suites[0].beforeAll(fn); - }; - - /** - * Execute after the suite. - */ - - context.suiteTeardown = function(fn){ - suites[0].afterAll(fn); - }; - - /** - * Describe a "suite" with the given `title` - * and callback `fn` containing nested suites - * and/or tests. - */ - - context.suite = function(title, fn){ - var suite = Suite.create(suites[0], title); - suites.unshift(suite); - fn(); - suites.shift(); - return suite; - }; - - /** - * Exclusive test-case. - */ - - context.suite.only = function(title, fn){ - var suite = context.suite(title, fn); - mocha.grep(suite.fullTitle()); - }; - - /** - * Describe a specification or test-case - * with the given `title` and callback `fn` - * acting as a thunk. - */ - - context.test = function(title, fn){ + context.test = function(title, fn) { var test = new Test(title, fn); + test.file = file; suites[0].addTest(test); return test; }; @@ -933,416 +1270,1114 @@ module.exports = function(suite){ * Exclusive test-case. */ - context.test.only = function(title, fn){ - var test = context.test(title, fn); - mocha.grep(test.fullTitle()); + context.test.only = function(title, fn) { + return common.test.only(mocha, context.test(title, fn)); }; + + context.test.skip = common.test.skip; + context.test.retries = common.test.retries; }); }; -}); // module: interfaces/tdd.js +module.exports.description = 'QUnit style'; + +},{"../suite":36,"../test":37,"./common":9}],13:[function(require,module,exports){ +'use strict'; + +var Test = require('../test'); +var EVENT_FILE_PRE_REQUIRE = require('../suite').constants + .EVENT_FILE_PRE_REQUIRE; + +/** + * TDD-style interface: + * + * suite('Array', function() { + * suite('#indexOf()', function() { + * suiteSetup(function() { + * + * }); + * + * test('should return -1 when not present', function() { + * + * }); + * + * test('should return the index when present', function() { + * + * }); + * + * suiteTeardown(function() { + * + * }); + * }); + * }); + * + * @param {Suite} suite Root suite. + */ +module.exports = function(suite) { + var suites = [suite]; + + suite.on(EVENT_FILE_PRE_REQUIRE, function(context, file, mocha) { + var common = require('./common')(suites, context, mocha); + + context.setup = common.beforeEach; + context.teardown = common.afterEach; + context.suiteSetup = common.before; + context.suiteTeardown = common.after; + context.run = mocha.options.delay && common.runWithSuite(suite); + + /** + * Describe a "suite" with the given `title` and callback `fn` containing + * nested suites and/or tests. + */ + context.suite = function(title, fn) { + return common.suite.create({ + title: title, + file: file, + fn: fn + }); + }; + + /** + * Pending suite. + */ + context.suite.skip = function(title, fn) { + return common.suite.skip({ + title: title, + file: file, + fn: fn + }); + }; + + /** + * Exclusive test-case. + */ + context.suite.only = function(title, fn) { + return common.suite.only({ + title: title, + file: file, + fn: fn + }); + }; + + /** + * Describe a specification or test-case with the given `title` and + * callback `fn` acting as a thunk. + */ + context.test = function(title, fn) { + var suite = suites[0]; + if (suite.isPending()) { + fn = null; + } + var test = new Test(title, fn); + test.file = file; + suite.addTest(test); + return test; + }; + + /** + * Exclusive test-case. + */ + + context.test.only = function(title, fn) { + return common.test.only(mocha, context.test(title, fn)); + }; + + context.test.skip = common.test.skip; + context.test.retries = common.test.retries; + }); +}; + +module.exports.description = + 'traditional "suite"/"test" instead of BDD\'s "describe"/"it"'; + +},{"../suite":36,"../test":37,"./common":9}],14:[function(require,module,exports){ +(function (process,global){ +'use strict'; -require.register("mocha.js", function(module, exports, require){ /*! * mocha * Copyright(c) 2011 TJ Holowaychuk * MIT Licensed */ -/** - * Module dependencies. - */ - -var path = require('browser/path') - , utils = require('./utils'); - -/** - * Expose `Mocha`. - */ +var escapeRe = require('escape-string-regexp'); +var path = require('path'); +var builtinReporters = require('./reporters'); +var growl = require('./growl'); +var utils = require('./utils'); +var mocharc = require('./mocharc.json'); +var errors = require('./errors'); +var Suite = require('./suite'); +var esmUtils = utils.supportsEsModules() ? require('./esm-utils') : undefined; +var createStatsCollector = require('./stats-collector'); +var createInvalidReporterError = errors.createInvalidReporterError; +var createInvalidInterfaceError = errors.createInvalidInterfaceError; +var EVENT_FILE_PRE_REQUIRE = Suite.constants.EVENT_FILE_PRE_REQUIRE; +var EVENT_FILE_POST_REQUIRE = Suite.constants.EVENT_FILE_POST_REQUIRE; +var EVENT_FILE_REQUIRE = Suite.constants.EVENT_FILE_REQUIRE; +var sQuote = utils.sQuote; exports = module.exports = Mocha; +/** + * To require local UIs and reporters when running in node. + */ + +if (!process.browser) { + var cwd = process.cwd(); + module.paths.push(cwd, path.join(cwd, 'node_modules')); +} + /** * Expose internals. */ +/** + * @public + * @class utils + * @memberof Mocha + */ exports.utils = utils; exports.interfaces = require('./interfaces'); -exports.reporters = require('./reporters'); +/** + * @public + * @memberof Mocha + */ +exports.reporters = builtinReporters; exports.Runnable = require('./runnable'); exports.Context = require('./context'); +/** + * + * @memberof Mocha + */ exports.Runner = require('./runner'); -exports.Suite = require('./suite'); +exports.Suite = Suite; exports.Hook = require('./hook'); exports.Test = require('./test'); /** - * Return image `name` path. + * Constructs a new Mocha instance with `options`. * - * @param {String} name - * @return {String} - * @api private + * @public + * @class Mocha + * @param {Object} [options] - Settings object. + * @param {boolean} [options.allowUncaught] - Propagate uncaught errors? + * @param {boolean} [options.asyncOnly] - Force `done` callback or promise? + * @param {boolean} [options.bail] - Bail after first test failure? + * @param {boolean} [options.checkLeaks] - Check for global variable leaks? + * @param {boolean} [options.color] - Color TTY output from reporter? + * @param {boolean} [options.delay] - Delay root suite execution? + * @param {boolean} [options.diff] - Show diff on failure? + * @param {string} [options.fgrep] - Test filter given string. + * @param {boolean} [options.forbidOnly] - Tests marked `only` fail the suite? + * @param {boolean} [options.forbidPending] - Pending tests fail the suite? + * @param {boolean} [options.fullTrace] - Full stacktrace upon failure? + * @param {string[]} [options.global] - Variables expected in global scope. + * @param {RegExp|string} [options.grep] - Test filter given regular expression. + * @param {boolean} [options.growl] - Enable desktop notifications? + * @param {boolean} [options.inlineDiffs] - Display inline diffs? + * @param {boolean} [options.invert] - Invert test filter matches? + * @param {boolean} [options.noHighlighting] - Disable syntax highlighting? + * @param {string|constructor} [options.reporter] - Reporter name or constructor. + * @param {Object} [options.reporterOption] - Reporter settings object. + * @param {number} [options.retries] - Number of times to retry failed tests. + * @param {number} [options.slow] - Slow threshold value. + * @param {number|string} [options.timeout] - Timeout threshold value. + * @param {string} [options.ui] - Interface name. */ - -function image(name) { - return __dirname + '/../images/' + name + '.png'; -} - -/** - * Setup mocha with `options`. - * - * Options: - * - * - `ui` name "bdd", "tdd", "exports" etc - * - `reporter` reporter instance, defaults to `mocha.reporters.Dot` - * - `globals` array of accepted globals - * - `timeout` timeout in milliseconds - * - `slow` milliseconds to wait before considering a test slow - * - `ignoreLeaks` ignore global leaks - * - `grep` string or regexp to filter tests with - * - * @param {Object} options - * @api public - */ - function Mocha(options) { - options = options || {}; + options = utils.assign({}, mocharc, options || {}); this.files = []; this.options = options; - this.grep(options.grep); - this.suite = new exports.Suite('', new exports.Context); - this.ui(options.ui); - this.reporter(options.reporter); - if (options.timeout) this.timeout(options.timeout); - if (options.slow) this.slow(options.slow); + // root suite + this.suite = new exports.Suite('', new exports.Context(), true); + + this.grep(options.grep) + .fgrep(options.fgrep) + .ui(options.ui) + .reporter( + options.reporter, + options.reporterOption || options.reporterOptions // reporterOptions was previously the only way to specify options to reporter + ) + .slow(options.slow) + .global(options.global); + + // this guard exists because Suite#timeout does not consider `undefined` to be valid input + if (typeof options.timeout !== 'undefined') { + this.timeout(options.timeout === false ? 0 : options.timeout); + } + + if ('retries' in options) { + this.retries(options.retries); + } + + [ + 'allowUncaught', + 'asyncOnly', + 'bail', + 'checkLeaks', + 'color', + 'delay', + 'diff', + 'forbidOnly', + 'forbidPending', + 'fullTrace', + 'growl', + 'inlineDiffs', + 'invert' + ].forEach(function(opt) { + if (options[opt]) { + this[opt](); + } + }, this); } /** - * Add test `file`. + * Enables or disables bailing on the first failure. * - * @param {String} file - * @api public + * @public + * @see [CLI option](../#-bail-b) + * @param {boolean} [bail=true] - Whether to bail on first error. + * @returns {Mocha} this + * @chainable */ +Mocha.prototype.bail = function(bail) { + this.suite.bail(bail !== false); + return this; +}; -Mocha.prototype.addFile = function(file){ +/** + * @summary + * Adds `file` to be loaded for execution. + * + * @description + * Useful for generic setup code that must be included within test suite. + * + * @public + * @see [CLI option](../#-file-filedirectoryglob) + * @param {string} file - Pathname of file to be loaded. + * @returns {Mocha} this + * @chainable + */ +Mocha.prototype.addFile = function(file) { this.files.push(file); return this; }; /** - * Set reporter to `reporter`, defaults to "dot". + * Sets reporter to `reporter`, defaults to "spec". * - * @param {String|Function} reporter name of a reporter or a reporter constructor - * @api public + * @public + * @see [CLI option](../#-reporter-name-r-name) + * @see [Reporters](../#reporters) + * @param {String|Function} reporter - Reporter name or constructor. + * @param {Object} [reporterOptions] - Options used to configure the reporter. + * @returns {Mocha} this + * @chainable + * @throws {Error} if requested reporter cannot be loaded + * @example + * + * // Use XUnit reporter and direct its output to file + * mocha.reporter('xunit', { output: '/path/to/testspec.xunit.xml' }); */ - -Mocha.prototype.reporter = function(reporter){ - if ('function' == typeof reporter) { +Mocha.prototype.reporter = function(reporter, reporterOptions) { + if (typeof reporter === 'function') { this._reporter = reporter; } else { - reporter = reporter || 'dot'; - try { - this._reporter = require('./reporters/' + reporter); - } catch (err) { - this._reporter = require(reporter); + reporter = reporter || 'spec'; + var _reporter; + // Try to load a built-in reporter. + if (builtinReporters[reporter]) { + _reporter = builtinReporters[reporter]; } - if (!this._reporter) throw new Error('invalid reporter "' + reporter + '"'); + // Try to load reporters from process.cwd() and node_modules + if (!_reporter) { + try { + _reporter = require(reporter); + } catch (err) { + if ( + err.code !== 'MODULE_NOT_FOUND' || + err.message.indexOf('Cannot find module') !== -1 + ) { + // Try to load reporters from a path (absolute or relative) + try { + _reporter = require(path.resolve(process.cwd(), reporter)); + } catch (_err) { + _err.code !== 'MODULE_NOT_FOUND' || + _err.message.indexOf('Cannot find module') !== -1 + ? console.warn(sQuote(reporter) + ' reporter not found') + : console.warn( + sQuote(reporter) + + ' reporter blew up with error:\n' + + err.stack + ); + } + } else { + console.warn( + sQuote(reporter) + ' reporter blew up with error:\n' + err.stack + ); + } + } + } + if (!_reporter) { + throw createInvalidReporterError( + 'invalid reporter ' + sQuote(reporter), + reporter + ); + } + this._reporter = _reporter; + } + this.options.reporterOption = reporterOptions; + // alias option name is used in public reporters xunit/tap/progress + this.options.reporterOptions = reporterOptions; + return this; +}; + +/** + * Sets test UI `name`, defaults to "bdd". + * + * @public + * @see [CLI option](../#-ui-name-u-name) + * @see [Interface DSLs](../#interfaces) + * @param {string|Function} [ui=bdd] - Interface name or class. + * @returns {Mocha} this + * @chainable + * @throws {Error} if requested interface cannot be loaded + */ +Mocha.prototype.ui = function(ui) { + var bindInterface; + if (typeof ui === 'function') { + bindInterface = ui; + } else { + ui = ui || 'bdd'; + bindInterface = exports.interfaces[ui]; + if (!bindInterface) { + try { + bindInterface = require(ui); + } catch (err) { + throw createInvalidInterfaceError( + 'invalid interface ' + sQuote(ui), + ui + ); + } + } + } + bindInterface(this.suite); + + this.suite.on(EVENT_FILE_PRE_REQUIRE, function(context) { + exports.afterEach = context.afterEach || context.teardown; + exports.after = context.after || context.suiteTeardown; + exports.beforeEach = context.beforeEach || context.setup; + exports.before = context.before || context.suiteSetup; + exports.describe = context.describe || context.suite; + exports.it = context.it || context.test; + exports.xit = context.xit || (context.test && context.test.skip); + exports.setup = context.setup || context.beforeEach; + exports.suiteSetup = context.suiteSetup || context.before; + exports.suiteTeardown = context.suiteTeardown || context.after; + exports.suite = context.suite || context.describe; + exports.teardown = context.teardown || context.afterEach; + exports.test = context.test || context.it; + exports.run = context.run; + }); + + return this; +}; + +/** + * Loads `files` prior to execution. Does not support ES Modules. + * + * @description + * The implementation relies on Node's `require` to execute + * the test interface functions and will be subject to its cache. + * Supports only CommonJS modules. To load ES modules, use Mocha#loadFilesAsync. + * + * @private + * @see {@link Mocha#addFile} + * @see {@link Mocha#run} + * @see {@link Mocha#unloadFiles} + * @see {@link Mocha#loadFilesAsync} + * @param {Function} [fn] - Callback invoked upon completion. + */ +Mocha.prototype.loadFiles = function(fn) { + var self = this; + var suite = this.suite; + this.files.forEach(function(file) { + file = path.resolve(file); + suite.emit(EVENT_FILE_PRE_REQUIRE, global, file, self); + suite.emit(EVENT_FILE_REQUIRE, require(file), file, self); + suite.emit(EVENT_FILE_POST_REQUIRE, global, file, self); + }); + fn && fn(); +}; + +/** + * Loads `files` prior to execution. Supports Node ES Modules. + * + * @description + * The implementation relies on Node's `require` and `import` to execute + * the test interface functions and will be subject to its cache. + * Supports both CJS and ESM modules. + * + * @public + * @see {@link Mocha#addFile} + * @see {@link Mocha#run} + * @see {@link Mocha#unloadFiles} + * @returns {Promise} + * @example + * + * // loads ESM (and CJS) test files asynchronously, then runs root suite + * mocha.loadFilesAsync() + * .then(() => mocha.run(failures => process.exitCode = failures ? 1 : 0)) + * .catch(() => process.exitCode = 1); + */ +Mocha.prototype.loadFilesAsync = function() { + var self = this; + var suite = this.suite; + this.loadAsync = true; + + if (!esmUtils) { + return new Promise(function(resolve) { + self.loadFiles(resolve); + }); + } + + return esmUtils.loadFilesAsync( + this.files, + function(file) { + suite.emit(EVENT_FILE_PRE_REQUIRE, global, file, self); + }, + function(file, resultModule) { + suite.emit(EVENT_FILE_REQUIRE, resultModule, file, self); + suite.emit(EVENT_FILE_POST_REQUIRE, global, file, self); + } + ); +}; + +/** + * Removes a previously loaded file from Node's `require` cache. + * + * @private + * @static + * @see {@link Mocha#unloadFiles} + * @param {string} file - Pathname of file to be unloaded. + */ +Mocha.unloadFile = function(file) { + delete require.cache[require.resolve(file)]; +}; + +/** + * Unloads `files` from Node's `require` cache. + * + * @description + * This allows required files to be "freshly" reloaded, providing the ability + * to reuse a Mocha instance programmatically. + * Note: does not clear ESM module files from the cache + * + * Intended for consumers — not used internally + * + * @public + * @see {@link Mocha#run} + * @returns {Mocha} this + * @chainable + */ +Mocha.prototype.unloadFiles = function() { + this.files.forEach(Mocha.unloadFile); + return this; +}; + +/** + * Sets `grep` filter after escaping RegExp special characters. + * + * @public + * @see {@link Mocha#grep} + * @param {string} str - Value to be converted to a regexp. + * @returns {Mocha} this + * @chainable + * @example + * + * // Select tests whose full title begins with `"foo"` followed by a period + * mocha.fgrep('foo.'); + */ +Mocha.prototype.fgrep = function(str) { + if (!str) { + return this; + } + return this.grep(new RegExp(escapeRe(str))); +}; + +/** + * @summary + * Sets `grep` filter used to select specific tests for execution. + * + * @description + * If `re` is a regexp-like string, it will be converted to regexp. + * The regexp is tested against the full title of each test (i.e., the + * name of the test preceded by titles of each its ancestral suites). + * As such, using an exact-match fixed pattern against the + * test name itself will not yield any matches. + *
        + * Previous filter value will be overwritten on each call! + * + * @public + * @see [CLI option](../#-grep-regexp-g-regexp) + * @see {@link Mocha#fgrep} + * @see {@link Mocha#invert} + * @param {RegExp|String} re - Regular expression used to select tests. + * @return {Mocha} this + * @chainable + * @example + * + * // Select tests whose full title contains `"match"`, ignoring case + * mocha.grep(/match/i); + * @example + * + * // Same as above but with regexp-like string argument + * mocha.grep('/match/i'); + * @example + * + * // ## Anti-example + * // Given embedded test `it('only-this-test')`... + * mocha.grep('/^only-this-test$/'); // NO! Use `.only()` to do this! + */ +Mocha.prototype.grep = function(re) { + if (utils.isString(re)) { + // extract args if it's regex-like, i.e: [string, pattern, flag] + var arg = re.match(/^\/(.*)\/(g|i|)$|.*/); + this.options.grep = new RegExp(arg[1] || arg[0], arg[2]); + } else { + this.options.grep = re; } return this; }; /** - * Set test UI `name`, defaults to "bdd". + * Inverts `grep` matches. * - * @param {String} bdd - * @api public - */ - -Mocha.prototype.ui = function(name){ - name = name || 'bdd'; - this._ui = exports.interfaces[name]; - if (!this._ui) throw new Error('invalid interface "' + name + '"'); - this._ui = this._ui(this.suite); - return this; -}; - -/** - * Load registered files. + * @public + * @see {@link Mocha#grep} + * @return {Mocha} this + * @chainable + * @example * - * @api private + * // Select tests whose full title does *not* contain `"match"`, ignoring case + * mocha.grep(/match/i).invert(); */ - -Mocha.prototype.loadFiles = function(fn){ - var self = this; - var suite = this.suite; - var pending = this.files.length; - this.files.forEach(function(file){ - file = path.resolve(file); - suite.emit('pre-require', global, file, self); - suite.emit('require', require(file), file, self); - suite.emit('post-require', global, file, self); - --pending || (fn && fn()); - }); -}; - -/** - * Enable growl support. - * - * @api private - */ - -Mocha.prototype._growl = function(runner, reporter) { - var notify = require('growl'); - - runner.on('end', function(){ - var stats = reporter.stats; - if (stats.failures) { - var msg = stats.failures + ' of ' + runner.total + ' tests failed'; - notify(msg, { name: 'mocha', title: 'Failed', image: image('error') }); - } else { - notify(stats.passes + ' tests passed in ' + stats.duration + 'ms', { - name: 'mocha' - , title: 'Passed' - , image: image('ok') - }); - } - }); -}; - -/** - * Add regexp to grep, if `re` is a string it is escaped. - * - * @param {RegExp|String} re - * @return {Mocha} - * @api public - */ - -Mocha.prototype.grep = function(re){ - this.options.grep = 'string' == typeof re - ? new RegExp(utils.escapeRegexp(re)) - : re; - return this; -}; - -/** - * Invert `.grep()` matches. - * - * @return {Mocha} - * @api public - */ - -Mocha.prototype.invert = function(){ +Mocha.prototype.invert = function() { this.options.invert = true; return this; }; /** - * Ignore global leaks. + * Enables or disables ignoring global leaks. * - * @return {Mocha} - * @api public + * @deprecated since v7.0.0 + * @public + * @see {@link Mocha#checkLeaks} + * @param {boolean} [ignoreLeaks=false] - Whether to ignore global leaks. + * @return {Mocha} this + * @chainable */ - -Mocha.prototype.ignoreLeaks = function(){ - this.options.ignoreLeaks = true; +Mocha.prototype.ignoreLeaks = function(ignoreLeaks) { + utils.deprecate( + '"ignoreLeaks()" is DEPRECATED, please use "checkLeaks()" instead.' + ); + this.options.checkLeaks = !ignoreLeaks; return this; }; /** - * Enable global leak checking. + * Enables or disables checking for global variables leaked while running tests. * - * @return {Mocha} - * @api public + * @public + * @see [CLI option](../#-check-leaks) + * @param {boolean} [checkLeaks=true] - Whether to check for global variable leaks. + * @return {Mocha} this + * @chainable */ - -Mocha.prototype.checkLeaks = function(){ - this.options.ignoreLeaks = false; +Mocha.prototype.checkLeaks = function(checkLeaks) { + this.options.checkLeaks = checkLeaks !== false; return this; }; /** - * Enable growl support. + * Displays full stack trace upon test failure. * - * @return {Mocha} - * @api public + * @public + * @see [CLI option](../#-full-trace) + * @param {boolean} [fullTrace=true] - Whether to print full stacktrace upon failure. + * @return {Mocha} this + * @chainable */ - -Mocha.prototype.growl = function(){ - this.options.growl = true; +Mocha.prototype.fullTrace = function(fullTrace) { + this.options.fullTrace = fullTrace !== false; return this; }; /** - * Ignore `globals` array or string. + * Enables desktop notification support if prerequisite software installed. * - * @param {Array|String} globals - * @return {Mocha} - * @api public + * @public + * @see [CLI option](../#-growl-g) + * @return {Mocha} this + * @chainable */ - -Mocha.prototype.globals = function(globals){ - this.options.globals = (this.options.globals || []).concat(globals); +Mocha.prototype.growl = function() { + this.options.growl = this.isGrowlCapable(); + if (!this.options.growl) { + var detail = process.browser + ? 'notification support not available in this browser...' + : 'notification support prerequisites not installed...'; + console.error(detail + ' cannot enable!'); + } return this; }; /** - * Set the timeout in milliseconds. + * @summary + * Determines if Growl support seems likely. * - * @param {Number} timeout - * @return {Mocha} - * @api public + * @description + * Not available when run in browser. + * + * @private + * @see {@link Growl#isCapable} + * @see {@link Mocha#growl} + * @return {boolean} whether Growl support can be expected */ +Mocha.prototype.isGrowlCapable = growl.isCapable; -Mocha.prototype.timeout = function(timeout){ - this.suite.timeout(timeout); +/** + * Implements desktop notifications using a pseudo-reporter. + * + * @private + * @see {@link Mocha#growl} + * @see {@link Growl#notify} + * @param {Runner} runner - Runner instance. + */ +Mocha.prototype._growl = growl.notify; + +/** + * Specifies whitelist of variable names to be expected in global scope. + * + * @public + * @see [CLI option](../#-global-variable-name) + * @see {@link Mocha#checkLeaks} + * @param {String[]|String} global - Accepted global variable name(s). + * @return {Mocha} this + * @chainable + * @example + * + * // Specify variables to be expected in global scope + * mocha.global(['jQuery', 'MyLib']); + */ +Mocha.prototype.global = function(global) { + this.options.global = (this.options.global || []) + .concat(global) + .filter(Boolean) + .filter(function(elt, idx, arr) { + return arr.indexOf(elt) === idx; + }); + return this; +}; +// for backwards compability, 'globals' is an alias of 'global' +Mocha.prototype.globals = Mocha.prototype.global; + +/** + * Enables or disables TTY color output by screen-oriented reporters. + * + * @deprecated since v7.0.0 + * @public + * @see {@link Mocha#color} + * @param {boolean} colors - Whether to enable color output. + * @return {Mocha} this + * @chainable + */ +Mocha.prototype.useColors = function(colors) { + utils.deprecate('"useColors()" is DEPRECATED, please use "color()" instead.'); + if (colors !== undefined) { + this.options.color = colors; + } return this; }; /** - * Set slowness threshold in milliseconds. + * Enables or disables TTY color output by screen-oriented reporters. * - * @param {Number} slow - * @return {Mocha} - * @api public + * @public + * @see [CLI option](../#-color-c-colors) + * @param {boolean} [color=true] - Whether to enable color output. + * @return {Mocha} this + * @chainable */ - -Mocha.prototype.slow = function(slow){ - this.suite.slow(slow); +Mocha.prototype.color = function(color) { + this.options.color = color !== false; return this; }; /** - * Run tests and invoke `fn()` when complete. + * Determines if reporter should use inline diffs (rather than +/-) + * in test failure output. * - * @param {Function} fn - * @return {Runner} - * @api public + * @deprecated since v7.0.0 + * @public + * @see {@link Mocha#inlineDiffs} + * @param {boolean} [inlineDiffs=false] - Whether to use inline diffs. + * @return {Mocha} this + * @chainable + */ +Mocha.prototype.useInlineDiffs = function(inlineDiffs) { + utils.deprecate( + '"useInlineDiffs()" is DEPRECATED, please use "inlineDiffs()" instead.' + ); + this.options.inlineDiffs = inlineDiffs !== undefined && inlineDiffs; + return this; +}; + +/** + * Enables or disables reporter to use inline diffs (rather than +/-) + * in test failure output. + * + * @public + * @see [CLI option](../#-inline-diffs) + * @param {boolean} [inlineDiffs=true] - Whether to use inline diffs. + * @return {Mocha} this + * @chainable + */ +Mocha.prototype.inlineDiffs = function(inlineDiffs) { + this.options.inlineDiffs = inlineDiffs !== false; + return this; +}; + +/** + * Determines if reporter should include diffs in test failure output. + * + * @deprecated since v7.0.0 + * @public + * @see {@link Mocha#diff} + * @param {boolean} [hideDiff=false] - Whether to hide diffs. + * @return {Mocha} this + * @chainable + */ +Mocha.prototype.hideDiff = function(hideDiff) { + utils.deprecate('"hideDiff()" is DEPRECATED, please use "diff()" instead.'); + this.options.diff = !(hideDiff === true); + return this; +}; + +/** + * Enables or disables reporter to include diff in test failure output. + * + * @public + * @see [CLI option](../#-diff) + * @param {boolean} [diff=true] - Whether to show diff on failure. + * @return {Mocha} this + * @chainable + */ +Mocha.prototype.diff = function(diff) { + this.options.diff = diff !== false; + return this; +}; + +/** + * @summary + * Sets timeout threshold value. + * + * @description + * A string argument can use shorthand (such as "2s") and will be converted. + * If the value is `0`, timeouts will be disabled. + * + * @public + * @see [CLI option](../#-timeout-ms-t-ms) + * @see [Timeouts](../#timeouts) + * @see {@link Mocha#enableTimeouts} + * @param {number|string} msecs - Timeout threshold value. + * @return {Mocha} this + * @chainable + * @example + * + * // Sets timeout to one second + * mocha.timeout(1000); + * @example + * + * // Same as above but using string argument + * mocha.timeout('1s'); + */ +Mocha.prototype.timeout = function(msecs) { + this.suite.timeout(msecs); + return this; +}; + +/** + * Sets the number of times to retry failed tests. + * + * @public + * @see [CLI option](../#-retries-n) + * @see [Retry Tests](../#retry-tests) + * @param {number} retry - Number of times to retry failed tests. + * @return {Mocha} this + * @chainable + * @example + * + * // Allow any failed test to retry one more time + * mocha.retries(1); + */ +Mocha.prototype.retries = function(n) { + this.suite.retries(n); + return this; +}; + +/** + * Sets slowness threshold value. + * + * @public + * @see [CLI option](../#-slow-ms-s-ms) + * @param {number} msecs - Slowness threshold value. + * @return {Mocha} this + * @chainable + * @example + * + * // Sets "slow" threshold to half a second + * mocha.slow(500); + * @example + * + * // Same as above but using string argument + * mocha.slow('0.5s'); + */ +Mocha.prototype.slow = function(msecs) { + this.suite.slow(msecs); + return this; +}; + +/** + * Enables or disables timeouts. + * + * @public + * @see [CLI option](../#-timeout-ms-t-ms) + * @param {boolean} enableTimeouts - Whether to enable timeouts. + * @return {Mocha} this + * @chainable + */ +Mocha.prototype.enableTimeouts = function(enableTimeouts) { + this.suite.enableTimeouts( + arguments.length && enableTimeouts !== undefined ? enableTimeouts : true + ); + return this; +}; + +/** + * Forces all tests to either accept a `done` callback or return a promise. + * + * @public + * @see [CLI option](../#-async-only-a) + * @param {boolean} [asyncOnly=true] - Wether to force `done` callback or promise. + * @return {Mocha} this + * @chainable + */ +Mocha.prototype.asyncOnly = function(asyncOnly) { + this.options.asyncOnly = asyncOnly !== false; + return this; +}; + +/** + * Disables syntax highlighting (in browser). + * + * @public + * @return {Mocha} this + * @chainable + */ +Mocha.prototype.noHighlighting = function() { + this.options.noHighlighting = true; + return this; +}; + +/** + * Enables or disables uncaught errors to propagate. + * + * @public + * @see [CLI option](../#-allow-uncaught) + * @param {boolean} [allowUncaught=true] - Whether to propagate uncaught errors. + * @return {Mocha} this + * @chainable + */ +Mocha.prototype.allowUncaught = function(allowUncaught) { + this.options.allowUncaught = allowUncaught !== false; + return this; +}; + +/** + * @summary + * Delays root suite execution. + * + * @description + * Used to perform asynch operations before any suites are run. + * + * @public + * @see [delayed root suite](../#delayed-root-suite) + * @returns {Mocha} this + * @chainable + */ +Mocha.prototype.delay = function delay() { + this.options.delay = true; + return this; +}; + +/** + * Causes tests marked `only` to fail the suite. + * + * @public + * @see [CLI option](../#-forbid-only) + * @param {boolean} [forbidOnly=true] - Whether tests marked `only` fail the suite. + * @returns {Mocha} this + * @chainable + */ +Mocha.prototype.forbidOnly = function(forbidOnly) { + this.options.forbidOnly = forbidOnly !== false; + return this; +}; + +/** + * Causes pending tests and tests marked `skip` to fail the suite. + * + * @public + * @see [CLI option](../#-forbid-pending) + * @param {boolean} [forbidPending=true] - Whether pending tests fail the suite. + * @returns {Mocha} this + * @chainable + */ +Mocha.prototype.forbidPending = function(forbidPending) { + this.options.forbidPending = forbidPending !== false; + return this; +}; + +/** + * Mocha version as specified by "package.json". + * + * @name Mocha#version + * @type string + * @readonly + */ +Object.defineProperty(Mocha.prototype, 'version', { + value: require('../package.json').version, + configurable: false, + enumerable: true, + writable: false +}); + +/** + * Callback to be invoked when test execution is complete. + * + * @callback DoneCB + * @param {number} failures - Number of failures that occurred. */ -Mocha.prototype.run = function(fn){ - if (this.files.length) this.loadFiles(); +/** + * Runs root suite and invokes `fn()` when complete. + * + * @description + * To run tests multiple times (or to run tests in files that are + * already in the `require` cache), make sure to clear them from + * the cache first! + * + * @public + * @see {@link Mocha#unloadFiles} + * @see {@link Runner#run} + * @param {DoneCB} [fn] - Callback invoked when test execution completed. + * @returns {Runner} runner instance + * @example + * + * // exit with non-zero status if there were test failures + * mocha.run(failures => process.exitCode = failures ? 1 : 0); + */ +Mocha.prototype.run = function(fn) { + if (this.files.length && !this.loadAsync) { + this.loadFiles(); + } var suite = this.suite; var options = this.options; - var runner = new exports.Runner(suite); - var reporter = new this._reporter(runner); - runner.ignoreLeaks = options.ignoreLeaks; - if (options.grep) runner.grep(options.grep, options.invert); - if (options.globals) runner.globals(options.globals); - if (options.growl) this._growl(runner, reporter); - return runner.run(fn); + options.files = this.files; + var runner = new exports.Runner(suite, options.delay); + createStatsCollector(runner); + var reporter = new this._reporter(runner, options); + runner.checkLeaks = options.checkLeaks === true; + runner.fullStackTrace = options.fullTrace; + runner.asyncOnly = options.asyncOnly; + runner.allowUncaught = options.allowUncaught; + runner.forbidOnly = options.forbidOnly; + runner.forbidPending = options.forbidPending; + if (options.grep) { + runner.grep(options.grep, options.invert); + } + if (options.global) { + runner.globals(options.global); + } + if (options.growl) { + this._growl(runner); + } + if (options.color !== undefined) { + exports.reporters.Base.useColors = options.color; + } + exports.reporters.Base.inlineDiffs = options.inlineDiffs; + exports.reporters.Base.hideDiff = !options.diff; + + function done(failures) { + fn = fn || utils.noop; + if (reporter.done) { + reporter.done(failures, fn); + } else { + fn(failures); + } + } + + return runner.run(done); }; -}); // module: mocha.js - -require.register("ms.js", function(module, exports, require){ - -/** - * Helpers. - */ - -var s = 1000; -var m = s * 60; -var h = m * 60; -var d = h * 24; - -/** - * Parse or format the given `val`. - * - * @param {String|Number} val - * @return {String|Number} - * @api public - */ - -module.exports = function(val){ - if ('string' == typeof val) return parse(val); - return format(val); +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"../package.json":90,"./context":5,"./errors":6,"./esm-utils":42,"./growl":2,"./hook":7,"./interfaces":11,"./mocharc.json":15,"./reporters":21,"./runnable":33,"./runner":34,"./stats-collector":35,"./suite":36,"./test":37,"./utils":38,"_process":69,"escape-string-regexp":49,"path":42}],15:[function(require,module,exports){ +module.exports={ + "diff": true, + "extension": ["js", "cjs", "mjs"], + "opts": "./test/mocha.opts", + "package": "./package.json", + "reporter": "spec", + "slow": 75, + "timeout": 2000, + "ui": "bdd", + "watch-ignore": ["node_modules", ".git"] } -/** - * Parse the given `str` and return milliseconds. - * - * @param {String} str - * @return {Number} - * @api private - */ +},{}],16:[function(require,module,exports){ +'use strict'; -function parse(str) { - var m = /^((?:\d+)?\.?\d+) *(ms|seconds?|s|minutes?|m|hours?|h|days?|d|years?|y)?$/i.exec(str); - if (!m) return; - var n = parseFloat(m[1]); - var type = (m[2] || 'ms').toLowerCase(); - switch (type) { - case 'years': - case 'year': - case 'y': - return n * 31557600000; - case 'days': - case 'day': - case 'd': - return n * 86400000; - case 'hours': - case 'hour': - case 'h': - return n * 3600000; - case 'minutes': - case 'minute': - case 'm': - return n * 60000; - case 'seconds': - case 'second': - case 's': - return n * 1000; - case 'ms': - return n; - } -} +module.exports = Pending; /** - * Format the given `ms`. + * Initialize a new `Pending` error with the given message. * - * @param {Number} ms - * @return {String} - * @api public + * @param {string} message */ - -function format(ms) { - if (ms == d) return (ms / d) + ' day'; - if (ms > d) return (ms / d) + ' days'; - if (ms == h) return (ms / h) + ' hour'; - if (ms > h) return (ms / h) + ' hours'; - if (ms == m) return (ms / m) + ' minute'; - if (ms > m) return (ms / m) + ' minutes'; - if (ms == s) return (ms / s) + ' second'; - if (ms > s) return (ms / s) + ' seconds'; - return ms + ' ms'; +function Pending(message) { + this.message = message; } -}); // module: ms.js - -require.register("reporters/base.js", function(module, exports, require){ +},{}],17:[function(require,module,exports){ +(function (process){ +'use strict'; +/** + * @module Base + */ /** * Module dependencies. */ -var tty = require('browser/tty') - , diff = require('browser/diff') - , ms = require('../ms'); - -/** - * Save timer references to avoid Sinon interfering (see GH-237). - */ - -var Date = global.Date - , setTimeout = global.setTimeout - , setInterval = global.setInterval - , clearTimeout = global.clearTimeout - , clearInterval = global.clearInterval; - -/** - * Check if both stdio streams are associated with a tty. - */ - -var isatty = tty.isatty(1) && tty.isatty(2); +var tty = require('tty'); +var diff = require('diff'); +var milliseconds = require('ms'); +var utils = require('../utils'); +var supportsColor = process.browser ? null : require('supports-color'); +var constants = require('../runner').constants; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; /** * Expose `Base`. @@ -1351,337 +2386,523 @@ var isatty = tty.isatty(1) && tty.isatty(2); exports = module.exports = Base; /** - * Enable coloring by default. + * Check if both stdio streams are associated with a tty. */ -exports.useColors = isatty; +var isatty = process.stdout.isTTY && process.stderr.isTTY; + +/** + * Save log references to avoid tests interfering (see GH-3604). + */ +var consoleLog = console.log; + +/** + * Enable coloring by default, except in the browser interface. + */ + +exports.useColors = + !process.browser && + (supportsColor.stdout || process.env.MOCHA_COLORS !== undefined); + +/** + * Inline diffs instead of +/- + */ + +exports.inlineDiffs = false; /** * Default color map. */ exports.colors = { - 'pass': 90 - , 'fail': 31 - , 'bright pass': 92 - , 'bright fail': 91 - , 'bright yellow': 93 - , 'pending': 36 - , 'suite': 0 - , 'error title': 0 - , 'error message': 31 - , 'error stack': 90 - , 'checkmark': 32 - , 'fast': 90 - , 'medium': 33 - , 'slow': 31 - , 'green': 32 - , 'light': 90 - , 'diff gutter': 90 - , 'diff added': 42 - , 'diff removed': 41 + pass: 90, + fail: 31, + 'bright pass': 92, + 'bright fail': 91, + 'bright yellow': 93, + pending: 36, + suite: 0, + 'error title': 0, + 'error message': 31, + 'error stack': 90, + checkmark: 32, + fast: 90, + medium: 33, + slow: 31, + green: 32, + light: 90, + 'diff gutter': 90, + 'diff added': 32, + 'diff removed': 31 }; +/** + * Default symbol map. + */ + +exports.symbols = { + ok: 'โœ“', + err: 'โœ–', + dot: 'โ€ค', + comma: ',', + bang: '!' +}; + +// With node.js on Windows: use symbols available in terminal default fonts +if (process.platform === 'win32') { + exports.symbols.ok = '\u221A'; + exports.symbols.err = '\u00D7'; + exports.symbols.dot = '.'; +} + /** * Color `str` with the given `type`, * allowing colors to be disabled, * as well as user-defined color * schemes. * - * @param {String} type - * @param {String} str - * @return {String} - * @api private + * @private + * @param {string} type + * @param {string} str + * @return {string} */ - -var color = exports.color = function(type, str) { - if (!exports.useColors) return str; +var color = (exports.color = function(type, str) { + if (!exports.useColors) { + return String(str); + } return '\u001b[' + exports.colors[type] + 'm' + str + '\u001b[0m'; -}; +}); /** - * Expose term window size, with some - * defaults for when stderr is not a tty. + * Expose term window size, with some defaults for when stderr is not a tty. */ exports.window = { - width: isatty - ? process.stdout.getWindowSize - ? process.stdout.getWindowSize(1)[0] - : tty.getWindowSize()[1] - : 75 + width: 75 }; +if (isatty) { + exports.window.width = process.stdout.getWindowSize + ? process.stdout.getWindowSize(1)[0] + : tty.getWindowSize()[1]; +} + /** - * Expose some basic cursor interactions - * that are common among reporters. + * Expose some basic cursor interactions that are common among reporters. */ exports.cursor = { - hide: function(){ - process.stdout.write('\u001b[?25l'); + hide: function() { + isatty && process.stdout.write('\u001b[?25l'); }, - show: function(){ - process.stdout.write('\u001b[?25h'); + show: function() { + isatty && process.stdout.write('\u001b[?25h'); }, - deleteLine: function(){ - process.stdout.write('\u001b[2K'); + deleteLine: function() { + isatty && process.stdout.write('\u001b[2K'); }, - beginningOfLine: function(){ - process.stdout.write('\u001b[0G'); + beginningOfLine: function() { + isatty && process.stdout.write('\u001b[0G'); }, - CR: function(){ - exports.cursor.deleteLine(); - exports.cursor.beginningOfLine(); + CR: function() { + if (isatty) { + exports.cursor.deleteLine(); + exports.cursor.beginningOfLine(); + } else { + process.stdout.write('\r'); + } } }; -/** - * Outut the given `failures` as a list. - * - * @param {Array} failures - * @api public - */ +var showDiff = (exports.showDiff = function(err) { + return ( + err && + err.showDiff !== false && + sameType(err.actual, err.expected) && + err.expected !== undefined + ); +}); -exports.list = function(failures){ - console.error(); - failures.forEach(function(test, i){ +function stringifyDiffObjs(err) { + if (!utils.isString(err.actual) || !utils.isString(err.expected)) { + err.actual = utils.stringify(err.actual); + err.expected = utils.stringify(err.expected); + } +} + +/** + * Returns a diff between 2 strings with coloured ANSI output. + * + * @description + * The diff will be either inline or unified dependent on the value + * of `Base.inlineDiff`. + * + * @param {string} actual + * @param {string} expected + * @return {string} Diff + */ +var generateDiff = (exports.generateDiff = function(actual, expected) { + try { + return exports.inlineDiffs + ? inlineDiff(actual, expected) + : unifiedDiff(actual, expected); + } catch (err) { + var msg = + '\n ' + + color('diff added', '+ expected') + + ' ' + + color('diff removed', '- actual: failed to generate Mocha diff') + + '\n'; + return msg; + } +}); + +/** + * Outputs the given `failures` as a list. + * + * @public + * @memberof Mocha.reporters.Base + * @variation 1 + * @param {Object[]} failures - Each is Test instance with corresponding + * Error property + */ +exports.list = function(failures) { + var multipleErr, multipleTest; + Base.consoleLog(); + failures.forEach(function(test, i) { // format - var fmt = color('error title', ' %s) %s:\n') - + color('error message', ' %s') - + color('error stack', '\n%s\n'); + var fmt = + color('error title', ' %s) %s:\n') + + color('error message', ' %s') + + color('error stack', '\n%s\n'); // msg - var err = test.err - , message = err.message || '' - , stack = err.stack || message - , index = stack.indexOf(message) + message.length - , msg = stack.slice(0, index) - , actual = err.actual - , expected = err.expected; - - // actual / expected diff - if ('string' == typeof actual && 'string' == typeof expected) { - var len = Math.max(actual.length, expected.length); - - if (len < 20) msg = errorDiff(err, 'Chars'); - else msg = errorDiff(err, 'Words'); - - // linenos - var lines = msg.split('\n'); - if (lines.length > 4) { - var width = String(lines.length).length; - msg = lines.map(function(str, i){ - return pad(++i, width) + ' |' + ' ' + str; - }).join('\n'); + var msg; + var err; + if (test.err && test.err.multiple) { + if (multipleTest !== test) { + multipleTest = test; + multipleErr = [test.err].concat(test.err.multiple); } + err = multipleErr.shift(); + } else { + err = test.err; + } + var message; + if (err.message && typeof err.message.toString === 'function') { + message = err.message + ''; + } else if (typeof err.inspect === 'function') { + message = err.inspect() + ''; + } else { + message = ''; + } + var stack = err.stack || message; + var index = message ? stack.indexOf(message) : -1; - // legend - msg = '\n' - + color('diff removed', 'actual') - + ' ' - + color('diff added', 'expected') - + '\n\n' - + msg - + '\n'; - - // indent - msg = msg.replace(/^/gm, ' '); - - fmt = color('error title', ' %s) %s:\n%s') - + color('error stack', '\n%s\n'); + if (index === -1) { + msg = message; + } else { + index += message.length; + msg = stack.slice(0, index); + // remove msg from stack + stack = stack.slice(index + 1); } - // indent stack trace without msg - stack = stack.slice(index ? index + 1 : index) - .replace(/^/gm, ' '); + // uncaught + if (err.uncaught) { + msg = 'Uncaught ' + msg; + } + // explicitly show diff + if (!exports.hideDiff && showDiff(err)) { + stringifyDiffObjs(err); + fmt = + color('error title', ' %s) %s:\n%s') + color('error stack', '\n%s\n'); + var match = message.match(/^([^:]+): expected/); + msg = '\n ' + color('error message', match ? match[1] : msg); - console.error(fmt, (i + 1), test.fullTitle(), msg, stack); + msg += generateDiff(err.actual, err.expected); + } + + // indent stack trace + stack = stack.replace(/^/gm, ' '); + + // indented test title + var testTitle = ''; + test.titlePath().forEach(function(str, index) { + if (index !== 0) { + testTitle += '\n '; + } + for (var i = 0; i < index; i++) { + testTitle += ' '; + } + testTitle += str; + }); + + Base.consoleLog(fmt, i + 1, testTitle, msg, stack); }); }; /** - * Initialize a new `Base` reporter. + * Constructs a new `Base` reporter instance. * - * All other reporters generally - * inherit from this reporter, providing - * stats such as test duration, number - * of tests passed / failed etc. + * @description + * All other reporters generally inherit from this reporter. * - * @param {Runner} runner - * @api public + * @public + * @class + * @memberof Mocha.reporters + * @param {Runner} runner - Instance triggers reporter actions. + * @param {Object} [options] - runner options */ +function Base(runner, options) { + var failures = (this.failures = []); -function Base(runner) { - var self = this - , stats = this.stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 } - , failures = this.failures = []; - - if (!runner) return; + if (!runner) { + throw new TypeError('Missing runner argument'); + } + this.options = options || {}; this.runner = runner; + this.stats = runner.stats; // assigned so Reporters keep a closer reference - runner.on('start', function(){ - stats.start = new Date; + runner.on(EVENT_TEST_PASS, function(test) { + if (test.duration > test.slow()) { + test.speed = 'slow'; + } else if (test.duration > test.slow() / 2) { + test.speed = 'medium'; + } else { + test.speed = 'fast'; + } }); - runner.on('suite', function(suite){ - stats.suites = stats.suites || 0; - suite.root || stats.suites++; - }); - - runner.on('test end', function(test){ - stats.tests = stats.tests || 0; - stats.tests++; - }); - - runner.on('pass', function(test){ - stats.passes = stats.passes || 0; - - var medium = test.slow() / 2; - test.speed = test.duration > test.slow() - ? 'slow' - : test.duration > medium - ? 'medium' - : 'fast'; - - stats.passes++; - }); - - runner.on('fail', function(test, err){ - stats.failures = stats.failures || 0; - stats.failures++; - test.err = err; + runner.on(EVENT_TEST_FAIL, function(test, err) { + if (showDiff(err)) { + stringifyDiffObjs(err); + } + // more than one error per test + if (test.err && err instanceof Error) { + test.err.multiple = (test.err.multiple || []).concat(err); + } else { + test.err = err; + } failures.push(test); }); - - runner.on('end', function(){ - stats.end = new Date; - stats.duration = new Date - stats.start; - }); - - runner.on('pending', function(){ - stats.pending++; - }); } /** - * Output common epilogue used by many of - * the bundled reporters. + * Outputs common epilogue used by many of the bundled reporters. * - * @api public + * @public + * @memberof Mocha.reporters */ +Base.prototype.epilogue = function() { + var stats = this.stats; + var fmt; -Base.prototype.epilogue = function(){ - var stats = this.stats - , fmt - , tests; + Base.consoleLog(); - console.log(); + // passes + fmt = + color('bright pass', ' ') + + color('green', ' %d passing') + + color('light', ' (%s)'); - function pluralize(n) { - return 1 == n ? 'test' : 'tests'; - } - - // failure - if (stats.failures) { - fmt = color('bright fail', ' โœ–') - + color('fail', ' %d of %d %s failed') - + color('light', ':') - - console.error(fmt, - stats.failures, - this.runner.total, - pluralize(this.runner.total)); - - Base.list(this.failures); - console.error(); - return; - } - - // pass - fmt = color('bright pass', ' โœ”') - + color('green', ' %d %s complete') - + color('light', ' (%s)'); - - console.log(fmt, - stats.tests || 0, - pluralize(stats.tests), - ms(stats.duration)); + Base.consoleLog(fmt, stats.passes || 0, milliseconds(stats.duration)); // pending if (stats.pending) { - fmt = color('pending', ' โ€ข') - + color('pending', ' %d %s pending'); + fmt = color('pending', ' ') + color('pending', ' %d pending'); - console.log(fmt, stats.pending, pluralize(stats.pending)); + Base.consoleLog(fmt, stats.pending); } - console.log(); + // failures + if (stats.failures) { + fmt = color('fail', ' %d failing'); + + Base.consoleLog(fmt, stats.failures); + + Base.list(this.failures); + Base.consoleLog(); + } + + Base.consoleLog(); }; /** - * Pad the given `str` to `len`. + * Pads the given `str` to `len`. * - * @param {String} str - * @param {String} len - * @return {String} - * @api private + * @private + * @param {string} str + * @param {string} len + * @return {string} */ - function pad(str, len) { str = String(str); return Array(len - str.length + 1).join(' ') + str; } /** - * Return a character diff for `err`. + * Returns inline diff between 2 strings with coloured ANSI output. * - * @param {Error} err - * @return {String} - * @api private + * @private + * @param {String} actual + * @param {String} expected + * @return {string} Diff */ +function inlineDiff(actual, expected) { + var msg = errorDiff(actual, expected); -function errorDiff(err, type) { - return diff['diff' + type](err.actual, err.expected).map(function(str){ - str.value = str.value - .replace(/\t/g, '') - .replace(/\r/g, '') - .replace(/\n/g, '\n'); - if (str.added) return colorLines('diff added', str.value); - if (str.removed) return colorLines('diff removed', str.value); - return str.value; - }).join(''); + // linenos + var lines = msg.split('\n'); + if (lines.length > 4) { + var width = String(lines.length).length; + msg = lines + .map(function(str, i) { + return pad(++i, width) + ' |' + ' ' + str; + }) + .join('\n'); + } + + // legend + msg = + '\n' + + color('diff removed', 'actual') + + ' ' + + color('diff added', 'expected') + + '\n\n' + + msg + + '\n'; + + // indent + msg = msg.replace(/^/gm, ' '); + return msg; } /** - * Color lines for `str`, using the color `name`. + * Returns unified diff between two strings with coloured ANSI output. * - * @param {String} name - * @param {String} str - * @return {String} - * @api private + * @private + * @param {String} actual + * @param {String} expected + * @return {string} The diff. */ - -function colorLines(name, str) { - return str.split('\n').map(function(str){ - return color(name, str); - }).join('\n'); +function unifiedDiff(actual, expected) { + var indent = ' '; + function cleanUp(line) { + if (line[0] === '+') { + return indent + colorLines('diff added', line); + } + if (line[0] === '-') { + return indent + colorLines('diff removed', line); + } + if (line.match(/@@/)) { + return '--'; + } + if (line.match(/\\ No newline/)) { + return null; + } + return indent + line; + } + function notBlank(line) { + return typeof line !== 'undefined' && line !== null; + } + var msg = diff.createPatch('string', actual, expected); + var lines = msg.split('\n').splice(5); + return ( + '\n ' + + colorLines('diff added', '+ expected') + + ' ' + + colorLines('diff removed', '- actual') + + '\n\n' + + lines + .map(cleanUp) + .filter(notBlank) + .join('\n') + ); } -}); // module: reporters/base.js +/** + * Returns character diff for `err`. + * + * @private + * @param {String} actual + * @param {String} expected + * @return {string} the diff + */ +function errorDiff(actual, expected) { + return diff + .diffWordsWithSpace(actual, expected) + .map(function(str) { + if (str.added) { + return colorLines('diff added', str.value); + } + if (str.removed) { + return colorLines('diff removed', str.value); + } + return str.value; + }) + .join(''); +} -require.register("reporters/doc.js", function(module, exports, require){ +/** + * Colors lines for `str`, using the color `name`. + * + * @private + * @param {string} name + * @param {string} str + * @return {string} + */ +function colorLines(name, str) { + return str + .split('\n') + .map(function(str) { + return color(name, str); + }) + .join('\n'); +} +/** + * Object#toString reference. + */ +var objToString = Object.prototype.toString; + +/** + * Checks that a / b have the same type. + * + * @private + * @param {Object} a + * @param {Object} b + * @return {boolean} + */ +function sameType(a, b) { + return objToString.call(a) === objToString.call(b); +} + +Base.consoleLog = consoleLog; + +Base.abstract = true; + +}).call(this,require('_process')) +},{"../runner":34,"../utils":38,"_process":69,"diff":48,"ms":60,"supports-color":42,"tty":4}],18:[function(require,module,exports){ +'use strict'; +/** + * @module Doc + */ /** * Module dependencies. */ -var Base = require('./base') - , utils = require('../utils'); +var Base = require('./base'); +var utils = require('../utils'); +var constants = require('../runner').constants; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; +var EVENT_SUITE_BEGIN = constants.EVENT_SUITE_BEGIN; +var EVENT_SUITE_END = constants.EVENT_SUITE_END; /** * Expose `Doc`. @@ -1690,58 +2911,91 @@ var Base = require('./base') exports = module.exports = Doc; /** - * Initialize a new `Doc` reporter. + * Constructs a new `Doc` reporter instance. * - * @param {Runner} runner - * @api public + * @public + * @class + * @memberof Mocha.reporters + * @extends Mocha.reporters.Base + * @param {Runner} runner - Instance triggers reporter actions. + * @param {Object} [options] - runner options */ +function Doc(runner, options) { + Base.call(this, runner, options); -function Doc(runner) { - Base.call(this, runner); - - var self = this - , stats = this.stats - , total = runner.total - , indents = 2; + var indents = 2; function indent() { return Array(indents).join(' '); } - runner.on('suite', function(suite){ - if (suite.root) return; + runner.on(EVENT_SUITE_BEGIN, function(suite) { + if (suite.root) { + return; + } ++indents; - console.log('%s
        ', indent()); + Base.consoleLog('%s
        ', indent()); ++indents; - console.log('%s

        %s

        ', indent(), suite.title); - console.log('%s
        ', indent()); + Base.consoleLog('%s

        %s

        ', indent(), utils.escape(suite.title)); + Base.consoleLog('%s
        ', indent()); }); - runner.on('suite end', function(suite){ - if (suite.root) return; - console.log('%s
        ', indent()); + runner.on(EVENT_SUITE_END, function(suite) { + if (suite.root) { + return; + } + Base.consoleLog('%s
        ', indent()); --indents; - console.log('%s
        ', indent()); + Base.consoleLog('%s
        ', indent()); --indents; }); - runner.on('pass', function(test){ - console.log('%s
        %s
        ', indent(), test.title); - var code = utils.escape(utils.clean(test.fn.toString())); - console.log('%s
        %s
        ', indent(), code); + runner.on(EVENT_TEST_PASS, function(test) { + Base.consoleLog('%s
        %s
        ', indent(), utils.escape(test.title)); + var code = utils.escape(utils.clean(test.body)); + Base.consoleLog('%s
        %s
        ', indent(), code); + }); + + runner.on(EVENT_TEST_FAIL, function(test, err) { + Base.consoleLog( + '%s
        %s
        ', + indent(), + utils.escape(test.title) + ); + var code = utils.escape(utils.clean(test.body)); + Base.consoleLog( + '%s
        %s
        ', + indent(), + code + ); + Base.consoleLog( + '%s
        %s
        ', + indent(), + utils.escape(err) + ); }); } -}); // module: reporters/doc.js - -require.register("reporters/dot.js", function(module, exports, require){ +Doc.description = 'HTML documentation'; +},{"../runner":34,"../utils":38,"./base":17}],19:[function(require,module,exports){ +(function (process){ +'use strict'; +/** + * @module Dot + */ /** * Module dependencies. */ -var Base = require('./base') - , color = Base.color; +var Base = require('./base'); +var inherits = require('../utils').inherits; +var constants = require('../runner').constants; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; +var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; +var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING; +var EVENT_RUN_END = constants.EVENT_RUN_END; /** * Expose `Dot`. @@ -1750,45 +3004,53 @@ var Base = require('./base') exports = module.exports = Dot; /** - * Initialize a new `Dot` matrix test reporter. + * Constructs a new `Dot` reporter instance. * - * @param {Runner} runner - * @api public + * @public + * @class + * @memberof Mocha.reporters + * @extends Mocha.reporters.Base + * @param {Runner} runner - Instance triggers reporter actions. + * @param {Object} [options] - runner options */ +function Dot(runner, options) { + Base.call(this, runner, options); -function Dot(runner) { - Base.call(this, runner); + var self = this; + var width = (Base.window.width * 0.75) | 0; + var n = -1; - var self = this - , stats = this.stats - , width = Base.window.width * .75 | 0 - , c = 'โ€ค' - , n = 0; - - runner.on('start', function(){ - process.stdout.write('\n '); + runner.on(EVENT_RUN_BEGIN, function() { + process.stdout.write('\n'); }); - runner.on('pending', function(test){ - process.stdout.write(color('pending', c)); + runner.on(EVENT_TEST_PENDING, function() { + if (++n % width === 0) { + process.stdout.write('\n '); + } + process.stdout.write(Base.color('pending', Base.symbols.comma)); }); - runner.on('pass', function(test){ - if (++n % width == 0) process.stdout.write('\n '); - if ('slow' == test.speed) { - process.stdout.write(color('bright yellow', c)); + runner.on(EVENT_TEST_PASS, function(test) { + if (++n % width === 0) { + process.stdout.write('\n '); + } + if (test.speed === 'slow') { + process.stdout.write(Base.color('bright yellow', Base.symbols.dot)); } else { - process.stdout.write(color(test.speed, c)); + process.stdout.write(Base.color(test.speed, Base.symbols.dot)); } }); - runner.on('fail', function(test, err){ - if (++n % width == 0) process.stdout.write('\n '); - process.stdout.write(color('fail', c)); + runner.on(EVENT_TEST_FAIL, function() { + if (++n % width === 0) { + process.stdout.write('\n '); + } + process.stdout.write(Base.color('fail', Base.symbols.bang)); }); - runner.on('end', function(){ - console.log(); + runner.once(EVENT_RUN_END, function() { + process.stdout.write('\n'); self.epilogue(); }); } @@ -1796,89 +3058,43 @@ function Dot(runner) { /** * Inherit from `Base.prototype`. */ +inherits(Dot, Base); -Dot.prototype = new Base; -Dot.prototype.constructor = Dot; +Dot.description = 'dot matrix representation'; -}); // module: reporters/dot.js - -require.register("reporters/html-cov.js", function(module, exports, require){ +}).call(this,require('_process')) +},{"../runner":34,"../utils":38,"./base":17,"_process":69}],20:[function(require,module,exports){ +(function (global){ +'use strict'; +/* eslint-env browser */ +/** + * @module HTML + */ /** * Module dependencies. */ -var JSONCov = require('./json-cov') - , fs = require('browser/fs'); - -/** - * Expose `HTMLCov`. - */ - -exports = module.exports = HTMLCov; - -/** - * Initialize a new `JsCoverage` reporter. - * - * @param {Runner} runner - * @api public - */ - -function HTMLCov(runner) { - var jade = require('jade') - , file = __dirname + '/templates/coverage.jade' - , str = fs.readFileSync(file, 'utf8') - , fn = jade.compile(str, { filename: file }) - , self = this; - - JSONCov.call(this, runner, false); - - runner.on('end', function(){ - process.stdout.write(fn({ - cov: self.cov - , coverageClass: coverageClass - })); - }); -} - -/** - * Return coverage class for `n`. - * - * @return {String} - * @api private - */ - -function coverageClass(n) { - if (n >= 75) return 'high'; - if (n >= 50) return 'medium'; - if (n >= 25) return 'low'; - return 'terrible'; -} -}); // module: reporters/html-cov.js - -require.register("reporters/html.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , utils = require('../utils') - , Progress = require('../browser/progress') - , escape = utils.escape; +var Base = require('./base'); +var utils = require('../utils'); +var Progress = require('../browser/progress'); +var escapeRe = require('escape-string-regexp'); +var constants = require('../runner').constants; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; +var EVENT_SUITE_BEGIN = constants.EVENT_SUITE_BEGIN; +var EVENT_SUITE_END = constants.EVENT_SUITE_END; +var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING; +var escape = utils.escape; /** * Save timer references to avoid Sinon interfering (see GH-237). */ -var Date = global.Date - , setTimeout = global.setTimeout - , setInterval = global.setInterval - , clearTimeout = global.clearTimeout - , clearInterval = global.clearInterval; +var Date = global.Date; /** - * Expose `Doc`. + * Expose `HTML`. */ exports = module.exports = HTML; @@ -1887,40 +3103,44 @@ exports = module.exports = HTML; * Stats template. */ -var statsTemplate = ''; +var statsTemplate = + ''; + +var playIcon = '‣'; /** - * Initialize a new `Doc` reporter. + * Constructs a new `HTML` reporter instance. * - * @param {Runner} runner - * @api public + * @public + * @class + * @memberof Mocha.reporters + * @extends Mocha.reporters.Base + * @param {Runner} runner - Instance triggers reporter actions. + * @param {Object} [options] - runner options */ +function HTML(runner, options) { + Base.call(this, runner, options); -function HTML(runner, root) { - Base.call(this, runner); - - var self = this - , stats = this.stats - , total = runner.total - , stat = fragment(statsTemplate) - , items = stat.getElementsByTagName('li') - , passes = items[1].getElementsByTagName('em')[0] - , passesLink = items[1].getElementsByTagName('a')[0] - , failures = items[2].getElementsByTagName('em')[0] - , failuresLink = items[2].getElementsByTagName('a')[0] - , duration = items[3].getElementsByTagName('em')[0] - , canvas = stat.getElementsByTagName('canvas')[0] - , report = fragment('
          ') - , stack = [report] - , progress - , ctx - - root = root || document.getElementById('mocha'); + var self = this; + var stats = this.stats; + var stat = fragment(statsTemplate); + var items = stat.getElementsByTagName('li'); + var passes = items[1].getElementsByTagName('em')[0]; + var passesLink = items[1].getElementsByTagName('a')[0]; + var failures = items[2].getElementsByTagName('em')[0]; + var failuresLink = items[2].getElementsByTagName('a')[0]; + var duration = items[3].getElementsByTagName('em')[0]; + var canvas = stat.getElementsByTagName('canvas')[0]; + var report = fragment('
            '); + var stack = [report]; + var progress; + var ctx; + var root = document.getElementById('mocha'); if (canvas.getContext) { var ratio = window.devicePixelRatio || 1; @@ -1930,34 +3150,54 @@ function HTML(runner, root) { canvas.height *= ratio; ctx = canvas.getContext('2d'); ctx.scale(ratio, ratio); - progress = new Progress; + progress = new Progress(); } - if (!root) return error('#mocha div missing, add it to your document'); + if (!root) { + return error('#mocha div missing, add it to your document'); + } // pass toggle - on(passesLink, 'click', function () { - var className = /pass/.test(report.className) ? '' : ' pass'; - report.className = report.className.replace(/fail|pass/g, '') + className; + on(passesLink, 'click', function(evt) { + evt.preventDefault(); + unhide(); + var name = /pass/.test(report.className) ? '' : ' pass'; + report.className = report.className.replace(/fail|pass/g, '') + name; + if (report.className.trim()) { + hideSuitesWithout('test pass'); + } }); // failure toggle - on(failuresLink, 'click', function () { - var className = /fail/.test(report.className) ? '' : ' fail'; - report.className = report.className.replace(/fail|pass/g, '') + className; + on(failuresLink, 'click', function(evt) { + evt.preventDefault(); + unhide(); + var name = /fail/.test(report.className) ? '' : ' fail'; + report.className = report.className.replace(/fail|pass/g, '') + name; + if (report.className.trim()) { + hideSuitesWithout('test fail'); + } }); root.appendChild(stat); root.appendChild(report); - if (progress) progress.size(40); + if (progress) { + progress.size(40); + } - runner.on('suite', function(suite){ - if (suite.root) return; + runner.on(EVENT_SUITE_BEGIN, function(suite) { + if (suite.root) { + return; + } // suite - var url = '?grep=' + encodeURIComponent(suite.fullTitle()); - var el = fragment('
          • %s

          • ', url, escape(suite.title)); + var url = self.suiteURL(suite); + var el = fragment( + '
          • %s

          • ', + url, + escape(suite.title) + ); // container stack[0].appendChild(el); @@ -1965,95 +3205,198 @@ function HTML(runner, root) { el.appendChild(stack[0]); }); - runner.on('suite end', function(suite){ - if (suite.root) return; + runner.on(EVENT_SUITE_END, function(suite) { + if (suite.root) { + updateStats(); + return; + } stack.shift(); }); - runner.on('fail', function(test, err){ - if ('hook' == test.type || err.uncaught) runner.emit('test end', test); + runner.on(EVENT_TEST_PASS, function(test) { + var url = self.testURL(test); + var markup = + '
          • %e%ems ' + + '' + + playIcon + + '

          • '; + var el = fragment(markup, test.speed, test.title, test.duration, url); + self.addCodeToggle(el, test.body); + appendToStack(el); + updateStats(); }); - runner.on('test end', function(test){ - window.scrollTo(0, document.body.scrollHeight); + runner.on(EVENT_TEST_FAIL, function(test) { + var el = fragment( + '
          • %e ' + + playIcon + + '

          • ', + test.title, + self.testURL(test) + ); + var stackString; // Note: Includes leading newline + var message = test.err.toString(); + // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we + // check for the result of the stringifying. + if (message === '[object Error]') { + message = test.err.message; + } + + if (test.err.stack) { + var indexOfMessage = test.err.stack.indexOf(test.err.message); + if (indexOfMessage === -1) { + stackString = test.err.stack; + } else { + stackString = test.err.stack.substr( + test.err.message.length + indexOfMessage + ); + } + } else if (test.err.sourceURL && test.err.line !== undefined) { + // Safari doesn't give you a stack. Let's at least provide a source line. + stackString = '\n(' + test.err.sourceURL + ':' + test.err.line + ')'; + } + + stackString = stackString || ''; + + if (test.err.htmlMessage && stackString) { + el.appendChild( + fragment( + '
            %s\n
            %e
            ', + test.err.htmlMessage, + stackString + ) + ); + } else if (test.err.htmlMessage) { + el.appendChild( + fragment('
            %s
            ', test.err.htmlMessage) + ); + } else { + el.appendChild( + fragment('
            %e%e
            ', message, stackString) + ); + } + + self.addCodeToggle(el, test.body); + appendToStack(el); + updateStats(); + }); + + runner.on(EVENT_TEST_PENDING, function(test) { + var el = fragment( + '
          • %e

          • ', + test.title + ); + appendToStack(el); + updateStats(); + }); + + function appendToStack(el) { + // Don't call .appendChild if #mocha-report was already .shift()'ed off the stack. + if (stack[0]) { + stack[0].appendChild(el); + } + } + + function updateStats() { // TODO: add to stats - var percent = stats.tests / total * 100 | 0; - if (progress) progress.update(percent).draw(ctx); + var percent = ((stats.tests / runner.total) * 100) | 0; + if (progress) { + progress.update(percent).draw(ctx); + } // update stats - var ms = new Date - stats.start; + var ms = new Date() - stats.start; text(passes, stats.passes); text(failures, stats.failures); text(duration, (ms / 1000).toFixed(2)); - - // test - if ('passed' == test.state) { - var el = fragment('
          • %e%ems

          • ', test.speed, test.title, test.duration); - } else if (test.pending) { - var el = fragment('
          • %e

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

          • ', test.title); - var str = test.err.stack || test.err.toString(); - - // FF / Opera do not add the message - if (!~str.indexOf(test.err.message)) { - str = test.err.message + '\n' + str; - } - - // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we - // check for the result of the stringifying. - if ('[object Error]' == str) str = test.err.message; - - // Safari doesn't give you a stack. Let's at least provide a source line. - if (!test.err.stack && test.err.sourceURL && test.err.line !== undefined) { - str += "\n(" + test.err.sourceURL + ":" + test.err.line + ")"; - } - - el.appendChild(fragment('
            %e
            ', str)); - } - - // toggle code - // TODO: defer - if (!test.pending) { - var h2 = el.getElementsByTagName('h2')[0]; - - on(h2, 'click', function(){ - pre.style.display = 'none' == pre.style.display - ? 'inline-block' - : 'none'; - }); - - var pre = fragment('
            %e
            ', utils.clean(test.fn.toString())); - el.appendChild(pre); - pre.style.display = 'none'; - } - - stack[0].appendChild(el); - }); + } } /** - * Display error `msg`. + * Makes a URL, preserving querystring ("search") parameters. + * + * @param {string} s + * @return {string} A new URL. */ +function makeUrl(s) { + var search = window.location.search; + // Remove previous grep query parameter if present + if (search) { + search = search.replace(/[?&]grep=[^&\s]*/g, '').replace(/^&/, '?'); + } + + return ( + window.location.pathname + + (search ? search + '&' : '?') + + 'grep=' + + encodeURIComponent(escapeRe(s)) + ); +} + +/** + * Provide suite URL. + * + * @param {Object} [suite] + */ +HTML.prototype.suiteURL = function(suite) { + return makeUrl(suite.fullTitle()); +}; + +/** + * Provide test URL. + * + * @param {Object} [test] + */ +HTML.prototype.testURL = function(test) { + return makeUrl(test.fullTitle()); +}; + +/** + * Adds code toggle functionality for the provided test's list element. + * + * @param {HTMLLIElement} el + * @param {string} contents + */ +HTML.prototype.addCodeToggle = function(el, contents) { + var h2 = el.getElementsByTagName('h2')[0]; + + on(h2, 'click', function() { + pre.style.display = pre.style.display === 'none' ? 'block' : 'none'; + }); + + var pre = fragment('
            %e
            ', utils.clean(contents)); + el.appendChild(pre); + pre.style.display = 'none'; +}; + +/** + * Display error `msg`. + * + * @param {string} msg + */ function error(msg) { - document.body.appendChild(fragment('
            %s
            ', msg)); + document.body.appendChild(fragment('
            %s
            ', msg)); } /** * Return a DOM fragment from `html`. + * + * @param {string} html */ - function fragment(html) { - var args = arguments - , div = document.createElement('div') - , i = 1; + var args = arguments; + var div = document.createElement('div'); + var i = 1; - div.innerHTML = html.replace(/%([se])/g, function(_, type){ + div.innerHTML = html.replace(/%([se])/g, function(_, type) { switch (type) { - case 's': return String(args[i++]); - case 'e': return escape(args[i++]); + case 's': + return String(args[i++]); + case 'e': + return escape(args[i++]); + // no default } }); @@ -2061,21 +3404,48 @@ function fragment(html) { } /** - * Set `el` text to `str`. + * Check for suites that do not have elements + * with `classname`, and hide them. + * + * @param {text} classname */ +function hideSuitesWithout(classname) { + var suites = document.getElementsByClassName('suite'); + for (var i = 0; i < suites.length; i++) { + var els = suites[i].getElementsByClassName(classname); + if (!els.length) { + suites[i].className += ' hidden'; + } + } +} -function text(el, str) { +/** + * Unhide .hidden suites. + */ +function unhide() { + var els = document.getElementsByClassName('suite hidden'); + while (els.length > 0) { + els[0].className = els[0].className.replace('suite hidden', 'suite'); + } +} + +/** + * Set an element's text contents. + * + * @param {HTMLElement} el + * @param {string} contents + */ +function text(el, contents) { if (el.textContent) { - el.textContent = str; + el.textContent = contents; } else { - el.innerText = str; + el.innerText = contents; } } /** * Listen on `event` with callback `fn`. */ - function on(el, event, fn) { if (el.addEventListener) { el.addEventListener(event, fn, false); @@ -2084,257 +3454,141 @@ function on(el, event, fn) { } } -}); // module: reporters/html.js +HTML.browserOnly = true; -require.register("reporters/index.js", function(module, exports, require){ +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"../browser/progress":3,"../runner":34,"../utils":38,"./base":17,"escape-string-regexp":49}],21:[function(require,module,exports){ +'use strict'; -exports.Base = require('./base'); -exports.Dot = require('./dot'); -exports.Doc = require('./doc'); -exports.TAP = require('./tap'); -exports.JSON = require('./json'); -exports.HTML = require('./html'); -exports.List = require('./list'); -exports.Min = require('./min'); -exports.Spec = require('./spec'); -exports.Nyan = require('./nyan'); -exports.XUnit = require('./xunit'); -exports.Markdown = require('./markdown'); -exports.Progress = require('./progress'); -exports.Landing = require('./landing'); -exports.JSONCov = require('./json-cov'); -exports.HTMLCov = require('./html-cov'); -exports.JSONStream = require('./json-stream'); -exports.Teamcity = require('./teamcity'); - -}); // module: reporters/index.js - -require.register("reporters/json-cov.js", function(module, exports, require){ +// Alias exports to a their normalized format Mocha#reporter to prevent a need +// for dynamic (try/catch) requires, which Browserify doesn't handle. +exports.Base = exports.base = require('./base'); +exports.Dot = exports.dot = require('./dot'); +exports.Doc = exports.doc = require('./doc'); +exports.TAP = exports.tap = require('./tap'); +exports.JSON = exports.json = require('./json'); +exports.HTML = exports.html = require('./html'); +exports.List = exports.list = require('./list'); +exports.Min = exports.min = require('./min'); +exports.Spec = exports.spec = require('./spec'); +exports.Nyan = exports.nyan = require('./nyan'); +exports.XUnit = exports.xunit = require('./xunit'); +exports.Markdown = exports.markdown = require('./markdown'); +exports.Progress = exports.progress = require('./progress'); +exports.Landing = exports.landing = require('./landing'); +exports.JSONStream = exports['json-stream'] = require('./json-stream'); +},{"./base":17,"./doc":18,"./dot":19,"./html":20,"./json":23,"./json-stream":22,"./landing":24,"./list":25,"./markdown":26,"./min":27,"./nyan":28,"./progress":29,"./spec":30,"./tap":31,"./xunit":32}],22:[function(require,module,exports){ +(function (process){ +'use strict'; +/** + * @module JSONStream + */ /** * Module dependencies. */ var Base = require('./base'); +var constants = require('../runner').constants; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; +var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; +var EVENT_RUN_END = constants.EVENT_RUN_END; /** - * Expose `JSONCov`. + * Expose `JSONStream`. */ -exports = module.exports = JSONCov; +exports = module.exports = JSONStream; /** - * Initialize a new `JsCoverage` reporter. + * Constructs a new `JSONStream` reporter instance. * - * @param {Runner} runner - * @param {Boolean} output - * @api public + * @public + * @class + * @memberof Mocha.reporters + * @extends Mocha.reporters.Base + * @param {Runner} runner - Instance triggers reporter actions. + * @param {Object} [options] - runner options */ +function JSONStream(runner, options) { + Base.call(this, runner, options); -function JSONCov(runner, output) { - var self = this - , output = 1 == arguments.length ? true : output; + var self = this; + var total = runner.total; - Base.call(this, runner); - - var tests = [] - , failures = [] - , passes = []; - - runner.on('test end', function(test){ - tests.push(test); + runner.once(EVENT_RUN_BEGIN, function() { + writeEvent(['start', {total: total}]); }); - runner.on('pass', function(test){ - passes.push(test); + runner.on(EVENT_TEST_PASS, function(test) { + writeEvent(['pass', clean(test)]); }); - runner.on('fail', function(test){ - failures.push(test); + runner.on(EVENT_TEST_FAIL, function(test, err) { + test = clean(test); + test.err = err.message; + test.stack = err.stack || null; + writeEvent(['fail', test]); }); - runner.on('end', function(){ - var cov = global._$jscoverage || {}; - var result = self.cov = map(cov); - result.stats = self.stats; - result.tests = tests.map(clean); - result.failures = failures.map(clean); - result.passes = passes.map(clean); - if (!output) return; - process.stdout.write(JSON.stringify(result, null, 2 )); + runner.once(EVENT_RUN_END, function() { + writeEvent(['end', self.stats]); }); } /** - * Map jscoverage data to a JSON structure - * suitable for reporting. - * - * @param {Object} cov - * @return {Object} - * @api private + * Mocha event to be written to the output stream. + * @typedef {Array} JSONStream~MochaEvent */ -function map(cov) { - var ret = { - instrumentation: 'node-jscoverage' - , sloc: 0 - , hits: 0 - , misses: 0 - , coverage: 0 - , files: [] - }; - - for (var filename in cov) { - var data = coverage(filename, cov[filename]); - ret.files.push(data); - ret.hits += data.hits; - ret.misses += data.misses; - ret.sloc += data.sloc; - } - - if (ret.sloc > 0) { - ret.coverage = (ret.hits / ret.sloc) * 100; - } - - return ret; -}; - /** - * Map jscoverage data for a single source file - * to a JSON structure suitable for reporting. + * Writes Mocha event to reporter output stream. * - * @param {String} filename name of the source file - * @param {Object} data jscoverage coverage data - * @return {Object} - * @api private + * @private + * @param {JSONStream~MochaEvent} event - Mocha event to be output. */ - -function coverage(filename, data) { - var ret = { - filename: filename, - coverage: 0, - hits: 0, - misses: 0, - sloc: 0, - source: {} - }; - - data.source.forEach(function(line, num){ - num++; - - if (data[num] === 0) { - ret.misses++; - ret.sloc++; - } else if (data[num] !== undefined) { - ret.hits++; - ret.sloc++; - } - - ret.source[num] = { - source: line - , coverage: data[num] === undefined - ? '' - : data[num] - }; - }); - - ret.coverage = ret.hits / ret.sloc * 100; - - return ret; +function writeEvent(event) { + process.stdout.write(JSON.stringify(event) + '\n'); } /** - * Return a plain-object representation of `test` - * free of cyclic properties etc. + * Returns an object literal representation of `test` + * free of cyclic properties, etc. * - * @param {Object} test - * @return {Object} - * @api private + * @private + * @param {Test} test - Instance used as data source. + * @return {Object} object containing pared-down test instance data */ - function clean(test) { return { - title: test.title - , fullTitle: test.fullTitle() - , duration: test.duration - } + title: test.title, + fullTitle: test.fullTitle(), + duration: test.duration, + currentRetry: test.currentRetry() + }; } -}); // module: reporters/json-cov.js - -require.register("reporters/json-stream.js", function(module, exports, require){ +JSONStream.description = 'newline delimited JSON events'; +}).call(this,require('_process')) +},{"../runner":34,"./base":17,"_process":69}],23:[function(require,module,exports){ +(function (process){ +'use strict'; +/** + * @module JSON + */ /** * Module dependencies. */ -var Base = require('./base') - , color = Base.color; - -/** - * Expose `List`. - */ - -exports = module.exports = List; - -/** - * Initialize a new `List` test reporter. - * - * @param {Runner} runner - * @api public - */ - -function List(runner) { - Base.call(this, runner); - - var self = this - , stats = this.stats - , total = runner.total; - - runner.on('start', function(){ - console.log(JSON.stringify(['start', { total: total }])); - }); - - runner.on('pass', function(test){ - console.log(JSON.stringify(['pass', clean(test)])); - }); - - runner.on('fail', function(test, err){ - console.log(JSON.stringify(['fail', clean(test)])); - }); - - runner.on('end', function(){ - process.stdout.write(JSON.stringify(['end', self.stats])); - }); -} - -/** - * Return a plain-object representation of `test` - * free of cyclic properties etc. - * - * @param {Object} test - * @return {Object} - * @api private - */ - -function clean(test) { - return { - title: test.title - , fullTitle: test.fullTitle() - , duration: test.duration - } -} -}); // module: reporters/json-stream.js - -require.register("reporters/json.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , cursor = Base.cursor - , color = Base.color; +var Base = require('./base'); +var constants = require('../runner').constants; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; +var EVENT_TEST_END = constants.EVENT_TEST_END; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING; /** * Expose `JSON`. @@ -2343,40 +3597,51 @@ var Base = require('./base') exports = module.exports = JSONReporter; /** - * Initialize a new `JSON` reporter. + * Constructs a new `JSON` reporter instance. * - * @param {Runner} runner - * @api public + * @public + * @class JSON + * @memberof Mocha.reporters + * @extends Mocha.reporters.Base + * @param {Runner} runner - Instance triggers reporter actions. + * @param {Object} [options] - runner options */ +function JSONReporter(runner, options) { + Base.call(this, runner, options); -function JSONReporter(runner) { var self = this; - Base.call(this, runner); + var tests = []; + var pending = []; + var failures = []; + var passes = []; - var tests = [] - , failures = [] - , passes = []; - - runner.on('test end', function(test){ + runner.on(EVENT_TEST_END, function(test) { tests.push(test); }); - runner.on('pass', function(test){ + runner.on(EVENT_TEST_PASS, function(test) { passes.push(test); }); - runner.on('fail', function(test){ + runner.on(EVENT_TEST_FAIL, function(test) { failures.push(test); }); - runner.on('end', function(){ + runner.on(EVENT_TEST_PENDING, function(test) { + pending.push(test); + }); + + runner.once(EVENT_RUN_END, function() { var obj = { - stats: self.stats - , tests: tests.map(clean) - , failures: failures.map(clean) - , passes: passes.map(clean) + stats: self.stats, + tests: tests.map(clean), + pending: pending.map(clean), + failures: failures.map(clean), + passes: passes.map(clean) }; + runner.testResults = obj; + process.stdout.write(JSON.stringify(obj, null, 2)); }); } @@ -2385,29 +3650,87 @@ function JSONReporter(runner) { * Return a plain-object representation of `test` * free of cyclic properties etc. * + * @private * @param {Object} test * @return {Object} - * @api private */ - function clean(test) { - return { - title: test.title - , fullTitle: test.fullTitle() - , duration: test.duration + var err = test.err || {}; + if (err instanceof Error) { + err = errorJSON(err); } + + return { + title: test.title, + fullTitle: test.fullTitle(), + duration: test.duration, + currentRetry: test.currentRetry(), + err: cleanCycles(err) + }; } -}); // module: reporters/json.js -require.register("reporters/landing.js", function(module, exports, require){ +/** + * Replaces any circular references inside `obj` with '[object Object]' + * + * @private + * @param {Object} obj + * @return {Object} + */ +function cleanCycles(obj) { + var cache = []; + return JSON.parse( + JSON.stringify(obj, function(key, value) { + if (typeof value === 'object' && value !== null) { + if (cache.indexOf(value) !== -1) { + // Instead of going in a circle, we'll print [object Object] + return '' + value; + } + cache.push(value); + } + return value; + }) + ); +} + +/** + * Transform an Error object into a JSON object. + * + * @private + * @param {Error} err + * @return {Object} + */ +function errorJSON(err) { + var res = {}; + Object.getOwnPropertyNames(err).forEach(function(key) { + res[key] = err[key]; + }, err); + return res; +} + +JSONReporter.description = 'single JSON object'; + +}).call(this,require('_process')) +},{"../runner":34,"./base":17,"_process":69}],24:[function(require,module,exports){ +(function (process){ +'use strict'; +/** + * @module Landing + */ /** * Module dependencies. */ -var Base = require('./base') - , cursor = Base.cursor - , color = Base.color; +var Base = require('./base'); +var inherits = require('../utils').inherits; +var constants = require('../runner').constants; +var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var EVENT_TEST_END = constants.EVENT_TEST_END; +var STATE_FAILED = require('../runnable').constants.STATE_FAILED; + +var cursor = Base.cursor; +var color = Base.color; /** * Expose `Landing`. @@ -2434,60 +3757,60 @@ Base.colors['plane crash'] = 31; Base.colors.runway = 90; /** - * Initialize a new `Landing` reporter. + * Constructs a new `Landing` reporter instance. * - * @param {Runner} runner - * @api public + * @public + * @class + * @memberof Mocha.reporters + * @extends Mocha.reporters.Base + * @param {Runner} runner - Instance triggers reporter actions. + * @param {Object} [options] - runner options */ +function Landing(runner, options) { + Base.call(this, runner, options); -function Landing(runner) { - Base.call(this, runner); - - var self = this - , stats = this.stats - , width = Base.window.width * .75 | 0 - , total = runner.total - , stream = process.stdout - , plane = color('plane', 'โœˆ') - , crashed = -1 - , n = 0; + var self = this; + var width = (Base.window.width * 0.75) | 0; + var total = runner.total; + var stream = process.stdout; + var plane = color('plane', 'โœˆ'); + var crashed = -1; + var n = 0; function runway() { var buf = Array(width).join('-'); return ' ' + color('runway', buf); } - runner.on('start', function(){ - stream.write('\n '); + runner.on(EVENT_RUN_BEGIN, function() { + stream.write('\n\n\n '); cursor.hide(); }); - runner.on('test end', function(test){ + runner.on(EVENT_TEST_END, function(test) { // check if the plane crashed - var col = -1 == crashed - ? width * ++n / total | 0 - : crashed; + var col = crashed === -1 ? ((width * ++n) / total) | 0 : crashed; // show the crash - if ('failed' == test.state) { + if (test.state === STATE_FAILED) { plane = color('plane crash', 'โœˆ'); crashed = col; } // render landing strip - stream.write('\u001b[4F\n\n'); + stream.write('\u001b[' + (width + 1) + 'D\u001b[2A'); stream.write(runway()); stream.write('\n '); stream.write(color('runway', Array(col).join('โ‹…'))); - stream.write(plane) + stream.write(plane); stream.write(color('runway', Array(width - col).join('โ‹…') + '\n')); stream.write(runway()); stream.write('\u001b[0m'); }); - runner.on('end', function(){ + runner.once(EVENT_RUN_END, function() { cursor.show(); - console.log(); + process.stdout.write('\n'); self.epilogue(); }); } @@ -2495,21 +3818,32 @@ function Landing(runner) { /** * Inherit from `Base.prototype`. */ +inherits(Landing, Base); -Landing.prototype = new Base; -Landing.prototype.constructor = Landing; - -}); // module: reporters/landing.js - -require.register("reporters/list.js", function(module, exports, require){ +Landing.description = 'Unicode landing strip'; +}).call(this,require('_process')) +},{"../runnable":33,"../runner":34,"../utils":38,"./base":17,"_process":69}],25:[function(require,module,exports){ +(function (process){ +'use strict'; +/** + * @module List + */ /** * Module dependencies. */ -var Base = require('./base') - , cursor = Base.cursor - , color = Base.color; +var Base = require('./base'); +var inherits = require('../utils').inherits; +var constants = require('../runner').constants; +var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var EVENT_TEST_BEGIN = constants.EVENT_TEST_BEGIN; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING; +var color = Base.color; +var cursor = Base.cursor; /** * Expose `List`. @@ -2518,66 +3852,82 @@ var Base = require('./base') exports = module.exports = List; /** - * Initialize a new `List` test reporter. + * Constructs a new `List` reporter instance. * - * @param {Runner} runner - * @api public + * @public + * @class + * @memberof Mocha.reporters + * @extends Mocha.reporters.Base + * @param {Runner} runner - Instance triggers reporter actions. + * @param {Object} [options] - runner options */ +function List(runner, options) { + Base.call(this, runner, options); -function List(runner) { - Base.call(this, runner); + var self = this; + var n = 0; - var self = this - , stats = this.stats - , n = 0; - - runner.on('start', function(){ - console.log(); + runner.on(EVENT_RUN_BEGIN, function() { + Base.consoleLog(); }); - runner.on('test', function(test){ + runner.on(EVENT_TEST_BEGIN, function(test) { process.stdout.write(color('pass', ' ' + test.fullTitle() + ': ')); }); - runner.on('pending', function(test){ - var fmt = color('checkmark', ' -') - + color('pending', ' %s'); - console.log(fmt, test.fullTitle()); + runner.on(EVENT_TEST_PENDING, function(test) { + var fmt = color('checkmark', ' -') + color('pending', ' %s'); + Base.consoleLog(fmt, test.fullTitle()); }); - runner.on('pass', function(test){ - var fmt = color('checkmark', ' โœ“') - + color('pass', ' %s: ') - + color(test.speed, '%dms'); + runner.on(EVENT_TEST_PASS, function(test) { + var fmt = + color('checkmark', ' ' + Base.symbols.ok) + + color('pass', ' %s: ') + + color(test.speed, '%dms'); cursor.CR(); - console.log(fmt, test.fullTitle(), test.duration); + Base.consoleLog(fmt, test.fullTitle(), test.duration); }); - runner.on('fail', function(test, err){ + runner.on(EVENT_TEST_FAIL, function(test) { cursor.CR(); - console.log(color('fail', ' %d) %s'), ++n, test.fullTitle()); + Base.consoleLog(color('fail', ' %d) %s'), ++n, test.fullTitle()); }); - runner.on('end', self.epilogue.bind(self)); + runner.once(EVENT_RUN_END, self.epilogue.bind(self)); } /** * Inherit from `Base.prototype`. */ +inherits(List, Base); -List.prototype = new Base; -List.prototype.constructor = List; +List.description = 'like "spec" reporter but flat'; - -}); // module: reporters/list.js - -require.register("reporters/markdown.js", function(module, exports, require){ +}).call(this,require('_process')) +},{"../runner":34,"../utils":38,"./base":17,"_process":69}],26:[function(require,module,exports){ +(function (process){ +'use strict'; +/** + * @module Markdown + */ /** * Module dependencies. */ -var Base = require('./base') - , utils = require('../utils'); +var Base = require('./base'); +var utils = require('../utils'); +var constants = require('../runner').constants; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var EVENT_SUITE_BEGIN = constants.EVENT_SUITE_BEGIN; +var EVENT_SUITE_END = constants.EVENT_SUITE_END; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; + +/** + * Constants + */ + +var SUITE_PREFIX = '$'; /** * Expose `Markdown`. @@ -2586,35 +3936,34 @@ var Base = require('./base') exports = module.exports = Markdown; /** - * Initialize a new `Markdown` reporter. + * Constructs a new `Markdown` reporter instance. * - * @param {Runner} runner - * @api public + * @public + * @class + * @memberof Mocha.reporters + * @extends Mocha.reporters.Base + * @param {Runner} runner - Instance triggers reporter actions. + * @param {Object} [options] - runner options */ +function Markdown(runner, options) { + Base.call(this, runner, options); -function Markdown(runner) { - Base.call(this, runner); - - var self = this - , stats = this.stats - , total = runner.total - , level = 0 - , buf = ''; + var level = 0; + var buf = ''; function title(str) { return Array(level).join('#') + ' ' + str; } - function indent() { - return Array(level).join(' '); - } - function mapTOC(suite, obj) { var ret = obj; - obj = obj[suite.title] = obj[suite.title] || { suite: suite }; - suite.suites.forEach(function(suite){ + var key = SUITE_PREFIX + suite.title; + + obj = obj[key] = obj[key] || {suite: suite}; + suite.suites.forEach(function(suite) { mapTOC(suite, obj); }); + return ret; } @@ -2623,12 +3972,16 @@ function Markdown(runner) { var buf = ''; var link; for (var key in obj) { - if ('suite' == key) continue; - if (key) link = ' - [' + key + '](#' + utils.slug(obj[key].suite.fullTitle()) + ')\n'; - if (key) buf += Array(level).join(' ') + link; + if (key === 'suite') { + continue; + } + if (key !== SUITE_PREFIX) { + link = ' - [' + key.substring(1) + ']'; + link += '(#' + utils.slug(obj[key].suite.fullTitle()) + ')\n'; + buf += Array(level).join(' ') + link; + } buf += stringifyTOC(obj[key], level); } - --level; return buf; } @@ -2639,40 +3992,50 @@ function Markdown(runner) { generateTOC(runner.suite); - runner.on('suite', function(suite){ + runner.on(EVENT_SUITE_BEGIN, function(suite) { ++level; var slug = utils.slug(suite.fullTitle()); - buf += '' + '\n'; + buf += '' + '\n'; buf += title(suite.title) + '\n'; }); - runner.on('suite end', function(suite){ + runner.on(EVENT_SUITE_END, function() { --level; }); - runner.on('pass', function(test){ - var code = utils.clean(test.fn.toString()); + runner.on(EVENT_TEST_PASS, function(test) { + var code = utils.clean(test.body); buf += test.title + '.\n'; buf += '\n```js\n'; buf += code + '\n'; buf += '```\n\n'; }); - runner.on('end', function(){ + runner.once(EVENT_RUN_END, function() { process.stdout.write('# TOC\n'); process.stdout.write(generateTOC(runner.suite)); process.stdout.write(buf); }); } -}); // module: reporters/markdown.js -require.register("reporters/min.js", function(module, exports, require){ +Markdown.description = 'GitHub Flavored Markdown'; +}).call(this,require('_process')) +},{"../runner":34,"../utils":38,"./base":17,"_process":69}],27:[function(require,module,exports){ +(function (process){ +'use strict'; +/** + * @module Min + */ /** * Module dependencies. */ var Base = require('./base'); +var inherits = require('../utils').inherits; +var constants = require('../runner').constants; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; /** * Expose `Min`. @@ -2681,42 +4044,57 @@ var Base = require('./base'); exports = module.exports = Min; /** - * Initialize a new `Min` minimal test reporter (best used with --watch). + * Constructs a new `Min` reporter instance. * - * @param {Runner} runner - * @api public + * @description + * This minimal test reporter is best used with '--watch'. + * + * @public + * @class + * @memberof Mocha.reporters + * @extends Mocha.reporters.Base + * @param {Runner} runner - Instance triggers reporter actions. + * @param {Object} [options] - runner options */ +function Min(runner, options) { + Base.call(this, runner, options); -function Min(runner) { - Base.call(this, runner); - - runner.on('start', function(){ + runner.on(EVENT_RUN_BEGIN, function() { // clear screen process.stdout.write('\u001b[2J'); // set cursor position process.stdout.write('\u001b[1;3H'); }); - runner.on('end', this.epilogue.bind(this)); + runner.once(EVENT_RUN_END, this.epilogue.bind(this)); } /** * Inherit from `Base.prototype`. */ +inherits(Min, Base); -Min.prototype = new Base; -Min.prototype.constructor = Min; - -}); // module: reporters/min.js - -require.register("reporters/nyan.js", function(module, exports, require){ +Min.description = 'essentially just a summary'; +}).call(this,require('_process')) +},{"../runner":34,"../utils":38,"./base":17,"_process":69}],28:[function(require,module,exports){ +(function (process){ +'use strict'; +/** + * @module Nyan + */ /** * Module dependencies. */ -var Base = require('./base') - , color = Base.color; +var Base = require('./base'); +var constants = require('../runner').constants; +var inherits = require('../utils').inherits; +var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; +var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; /** * Expose `Dot`. @@ -2725,64 +4103,72 @@ var Base = require('./base') exports = module.exports = NyanCat; /** - * Initialize a new `Dot` matrix test reporter. + * Constructs a new `Nyan` reporter instance. * - * @param {Runner} runner - * @api public + * @public + * @class Nyan + * @memberof Mocha.reporters + * @extends Mocha.reporters.Base + * @param {Runner} runner - Instance triggers reporter actions. + * @param {Object} [options] - runner options */ +function NyanCat(runner, options) { + Base.call(this, runner, options); -function NyanCat(runner) { - Base.call(this, runner); + var self = this; + var width = (Base.window.width * 0.75) | 0; + var nyanCatWidth = (this.nyanCatWidth = 11); - var self = this - , stats = this.stats - , width = Base.window.width * .75 | 0 - , rainbowColors = this.rainbowColors = self.generateColors() - , colorIndex = this.colorIndex = 0 - , numerOfLines = this.numberOfLines = 4 - , trajectories = this.trajectories = [[], [], [], []] - , nyanCatWidth = this.nyanCatWidth = 11 - , trajectoryWidthMax = this.trajectoryWidthMax = (width - nyanCatWidth) - , scoreboardWidth = this.scoreboardWidth = 5 - , tick = this.tick = 0 - , n = 0; + this.colorIndex = 0; + this.numberOfLines = 4; + this.rainbowColors = self.generateColors(); + this.scoreboardWidth = 5; + this.tick = 0; + this.trajectories = [[], [], [], []]; + this.trajectoryWidthMax = width - nyanCatWidth; - runner.on('start', function(){ + runner.on(EVENT_RUN_BEGIN, function() { Base.cursor.hide(); - self.draw('start'); + self.draw(); }); - runner.on('pending', function(test){ - self.draw('pending'); + runner.on(EVENT_TEST_PENDING, function() { + self.draw(); }); - runner.on('pass', function(test){ - self.draw('pass'); + runner.on(EVENT_TEST_PASS, function() { + self.draw(); }); - runner.on('fail', function(test, err){ - self.draw('fail'); + runner.on(EVENT_TEST_FAIL, function() { + self.draw(); }); - runner.on('end', function(){ + runner.once(EVENT_RUN_END, function() { Base.cursor.show(); - for (var i = 0; i < self.numberOfLines; i++) write('\n'); + for (var i = 0; i < self.numberOfLines; i++) { + write('\n'); + } self.epilogue(); }); } /** - * Draw the nyan cat with runner `status`. + * Inherit from `Base.prototype`. + */ +inherits(NyanCat, Base); + +/** + * Draw the nyan cat * - * @param {String} status - * @api private + * @private */ -NyanCat.prototype.draw = function(status){ +NyanCat.prototype.draw = function() { this.appendRainbow(); this.drawScoreboard(); this.drawRainbow(); - this.drawNyanCat(status); + this.drawNyanCat(); this.tick = !this.tick; }; @@ -2790,22 +4176,21 @@ NyanCat.prototype.draw = function(status){ * Draw the "scoreboard" showing the number * of passes, failures and pending tests. * - * @api private + * @private */ -NyanCat.prototype.drawScoreboard = function(){ +NyanCat.prototype.drawScoreboard = function() { var stats = this.stats; - var colors = Base.colors; - function draw(color, n) { + function draw(type, n) { write(' '); - write('\u001b[' + color + 'm' + n + '\u001b[0m'); + write(Base.color(type, n)); write('\n'); } - draw(colors.green, stats.passes); - draw(colors.fail, stats.failures); - draw(colors.pending, stats.pending); + draw('green', stats.passes); + draw('fail', stats.failures); + draw('pending', stats.pending); write('\n'); this.cursorUp(this.numberOfLines); @@ -2814,16 +4199,18 @@ NyanCat.prototype.drawScoreboard = function(){ /** * Append the rainbow. * - * @api private + * @private */ -NyanCat.prototype.appendRainbow = function(){ +NyanCat.prototype.appendRainbow = function() { var segment = this.tick ? '_' : '-'; var rainbowified = this.rainbowify(segment); for (var index = 0; index < this.numberOfLines; index++) { var trajectory = this.trajectories[index]; - if (trajectory.length >= this.trajectoryWidthMax) trajectory.shift(); + if (trajectory.length >= this.trajectoryWidthMax) { + trajectory.shift(); + } trajectory.push(rainbowified); } }; @@ -2831,13 +4218,13 @@ NyanCat.prototype.appendRainbow = function(){ /** * Draw the rainbow. * - * @api private + * @private */ -NyanCat.prototype.drawRainbow = function(){ +NyanCat.prototype.drawRainbow = function() { var self = this; - this.trajectories.forEach(function(line, index) { + this.trajectories.forEach(function(line) { write('\u001b[' + self.scoreboardWidth + 'C'); write(line.join('')); write('\n'); @@ -2847,62 +4234,63 @@ NyanCat.prototype.drawRainbow = function(){ }; /** - * Draw the nyan cat with `status`. + * Draw the nyan cat * - * @param {String} status - * @api private + * @private */ - -NyanCat.prototype.drawNyanCat = function(status) { +NyanCat.prototype.drawNyanCat = function() { var self = this; var startWidth = this.scoreboardWidth + this.trajectories[0].length; + var dist = '\u001b[' + startWidth + 'C'; + var padding = ''; - [0, 1, 2, 3].forEach(function(index) { - write('\u001b[' + startWidth + 'C'); + write(dist); + write('_,------,'); + write('\n'); - switch (index) { - case 0: - write('_,------,'); - write('\n'); - break; - case 1: - var padding = self.tick ? ' ' : ' '; - write('_|' + padding + '/\\_/\\ '); - write('\n'); - break; - case 2: - var padding = self.tick ? '_' : '__'; - var tail = self.tick ? '~' : '^'; - var face; - switch (status) { - case 'pass': - face = '( ^ .^)'; - break; - case 'fail': - face = '( o .o)'; - break; - default: - face = '( - .-)'; - } - write(tail + '|' + padding + face + ' '); - write('\n'); - break; - case 3: - var padding = self.tick ? ' ' : ' '; - write(padding + '"" "" '); - write('\n'); - break; - } - }); + write(dist); + padding = self.tick ? ' ' : ' '; + write('_|' + padding + '/\\_/\\ '); + write('\n'); + + write(dist); + padding = self.tick ? '_' : '__'; + var tail = self.tick ? '~' : '^'; + write(tail + '|' + padding + this.face() + ' '); + write('\n'); + + write(dist); + padding = self.tick ? ' ' : ' '; + write(padding + '"" "" '); + write('\n'); this.cursorUp(this.numberOfLines); }; +/** + * Draw nyan cat face. + * + * @private + * @return {string} + */ + +NyanCat.prototype.face = function() { + var stats = this.stats; + if (stats.failures) { + return '( x .x)'; + } else if (stats.pending) { + return '( o .o)'; + } else if (stats.passes) { + return '( ^ .^)'; + } + return '( - .-)'; +}; + /** * Move cursor up `n`. * - * @param {Number} n - * @api private + * @private + * @param {number} n */ NyanCat.prototype.cursorUp = function(n) { @@ -2912,8 +4300,8 @@ NyanCat.prototype.cursorUp = function(n) { /** * Move cursor down `n`. * - * @param {Number} n - * @api private + * @private + * @param {number} n */ NyanCat.prototype.cursorDown = function(n) { @@ -2923,16 +4311,15 @@ NyanCat.prototype.cursorDown = function(n) { /** * Generate rainbow colors. * + * @private * @return {Array} - * @api private */ - -NyanCat.prototype.generateColors = function(){ +NyanCat.prototype.generateColors = function() { var colors = []; - for (var i = 0; i < (6 * 7); i++) { + for (var i = 0; i < 6 * 7; i++) { var pi3 = Math.floor(Math.PI / 3); - var n = (i * (1.0 / 6)); + var n = i * (1.0 / 6); var r = Math.floor(3 * Math.sin(n) + 3); var g = Math.floor(3 * Math.sin(n + 2 * pi3) + 3); var b = Math.floor(3 * Math.sin(n + 4 * pi3) + 3); @@ -2945,12 +4332,14 @@ NyanCat.prototype.generateColors = function(){ /** * Apply rainbow to the given `str`. * - * @param {String} str - * @return {String} - * @api private + * @private + * @param {string} str + * @return {string} */ - -NyanCat.prototype.rainbowify = function(str){ +NyanCat.prototype.rainbowify = function(str) { + if (!Base.useColors) { + return str; + } var color = this.rainbowColors[this.colorIndex % this.rainbowColors.length]; this.colorIndex += 1; return '\u001b[38;5;' + color + 'm' + str + '\u001b[0m'; @@ -2958,31 +4347,34 @@ NyanCat.prototype.rainbowify = function(str){ /** * Stdout helper. + * + * @param {string} string A message to write to stdout. */ - function write(string) { process.stdout.write(string); } +NyanCat.description = '"nyan cat"'; + +}).call(this,require('_process')) +},{"../runner":34,"../utils":38,"./base":17,"_process":69}],29:[function(require,module,exports){ +(function (process){ +'use strict'; /** - * Inherit from `Base.prototype`. + * @module Progress */ - -NyanCat.prototype = new Base; -NyanCat.prototype.constructor = NyanCat; - - -}); // module: reporters/nyan.js - -require.register("reporters/progress.js", function(module, exports, require){ - /** * Module dependencies. */ -var Base = require('./base') - , cursor = Base.cursor - , color = Base.color; +var Base = require('./base'); +var constants = require('../runner').constants; +var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; +var EVENT_TEST_END = constants.EVENT_TEST_END; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var inherits = require('../utils').inherits; +var color = Base.color; +var cursor = Base.cursor; /** * Expose `Progress`. @@ -2997,44 +4389,53 @@ exports = module.exports = Progress; Base.colors.progress = 90; /** - * Initialize a new `Progress` bar test reporter. + * Constructs a new `Progress` reporter instance. * - * @param {Runner} runner - * @param {Object} options - * @api public + * @public + * @class + * @memberof Mocha.reporters + * @extends Mocha.reporters.Base + * @param {Runner} runner - Instance triggers reporter actions. + * @param {Object} [options] - runner options */ - function Progress(runner, options) { - Base.call(this, runner); + Base.call(this, runner, options); - var self = this - , options = options || {} - , stats = this.stats - , width = Base.window.width * .50 | 0 - , total = runner.total - , complete = 0 - , max = Math.max; + var self = this; + var width = (Base.window.width * 0.5) | 0; + var total = runner.total; + var complete = 0; + var lastN = -1; // default chars - options.open = options.open || '['; - options.complete = options.complete || 'โ–ฌ'; - options.incomplete = options.incomplete || 'โ‹…'; - options.close = options.close || ']'; - options.verbose = false; + options = options || {}; + var reporterOptions = options.reporterOptions || {}; + + options.open = reporterOptions.open || '['; + options.complete = reporterOptions.complete || 'โ–ฌ'; + options.incomplete = reporterOptions.incomplete || Base.symbols.dot; + options.close = reporterOptions.close || ']'; + options.verbose = reporterOptions.verbose || false; // tests started - runner.on('start', function(){ - console.log(); + runner.on(EVENT_RUN_BEGIN, function() { + process.stdout.write('\n'); cursor.hide(); }); // tests complete - runner.on('test end', function(){ + runner.on(EVENT_TEST_END, function() { complete++; - var incomplete = total - complete - , percent = complete / total - , n = width * percent | 0 - , i = width - n; + + var percent = complete / total; + var n = (width * percent) | 0; + var i = width - n; + + if (n === lastN && !options.verbose) { + // Don't re-render the line if it hasn't changed + return; + } + lastN = n; cursor.CR(); process.stdout.write('\u001b[J'); @@ -3049,9 +4450,9 @@ function Progress(runner, options) { // tests are complete, output some stats // and the failures if any - runner.on('end', function(){ + runner.once(EVENT_RUN_END, function() { cursor.show(); - console.log(); + process.stdout.write('\n'); self.epilogue(); }); } @@ -3059,22 +4460,31 @@ function Progress(runner, options) { /** * Inherit from `Base.prototype`. */ +inherits(Progress, Base); -Progress.prototype = new Base; -Progress.prototype.constructor = Progress; - - -}); // module: reporters/progress.js - -require.register("reporters/spec.js", function(module, exports, require){ +Progress.description = 'a progress bar'; +}).call(this,require('_process')) +},{"../runner":34,"../utils":38,"./base":17,"_process":69}],30:[function(require,module,exports){ +'use strict'; +/** + * @module Spec + */ /** * Module dependencies. */ -var Base = require('./base') - , cursor = Base.cursor - , color = Base.color; +var Base = require('./base'); +var constants = require('../runner').constants; +var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var EVENT_SUITE_BEGIN = constants.EVENT_SUITE_BEGIN; +var EVENT_SUITE_END = constants.EVENT_SUITE_END; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING; +var inherits = require('../utils').inherits; +var color = Base.color; /** * Expose `Spec`. @@ -3083,91 +4493,100 @@ var Base = require('./base') exports = module.exports = Spec; /** - * Initialize a new `Spec` test reporter. + * Constructs a new `Spec` reporter instance. * - * @param {Runner} runner - * @api public + * @public + * @class + * @memberof Mocha.reporters + * @extends Mocha.reporters.Base + * @param {Runner} runner - Instance triggers reporter actions. + * @param {Object} [options] - runner options */ +function Spec(runner, options) { + Base.call(this, runner, options); -function Spec(runner) { - Base.call(this, runner); - - var self = this - , stats = this.stats - , indents = 0 - , n = 0; + var self = this; + var indents = 0; + var n = 0; function indent() { - return Array(indents).join(' ') + return Array(indents).join(' '); } - runner.on('start', function(){ - console.log(); + runner.on(EVENT_RUN_BEGIN, function() { + Base.consoleLog(); }); - runner.on('suite', function(suite){ + runner.on(EVENT_SUITE_BEGIN, function(suite) { ++indents; - console.log(color('suite', '%s%s'), indent(), suite.title); + Base.consoleLog(color('suite', '%s%s'), indent(), suite.title); }); - runner.on('suite end', function(suite){ + runner.on(EVENT_SUITE_END, function() { --indents; - if (1 == indents) console.log(); - }); - - runner.on('test', function(test){ - process.stdout.write(indent() + color('pass', ' โ—ฆ ' + test.title + ': ')); - }); - - runner.on('pending', function(test){ - var fmt = indent() + color('pending', ' - %s'); - console.log(fmt, test.title); - }); - - runner.on('pass', function(test){ - if ('fast' == test.speed) { - var fmt = indent() - + color('checkmark', ' โœ“') - + color('pass', ' %s '); - cursor.CR(); - console.log(fmt, test.title); - } else { - var fmt = indent() - + color('checkmark', ' โœ“') - + color('pass', ' %s ') - + color(test.speed, '(%dms)'); - cursor.CR(); - console.log(fmt, test.title, test.duration); + if (indents === 1) { + Base.consoleLog(); } }); - runner.on('fail', function(test, err){ - cursor.CR(); - console.log(indent() + color('fail', ' %d) %s'), ++n, test.title); + runner.on(EVENT_TEST_PENDING, function(test) { + var fmt = indent() + color('pending', ' - %s'); + Base.consoleLog(fmt, test.title); }); - runner.on('end', self.epilogue.bind(self)); + runner.on(EVENT_TEST_PASS, function(test) { + var fmt; + if (test.speed === 'fast') { + fmt = + indent() + + color('checkmark', ' ' + Base.symbols.ok) + + color('pass', ' %s'); + Base.consoleLog(fmt, test.title); + } else { + fmt = + indent() + + color('checkmark', ' ' + Base.symbols.ok) + + color('pass', ' %s') + + color(test.speed, ' (%dms)'); + Base.consoleLog(fmt, test.title, test.duration); + } + }); + + runner.on(EVENT_TEST_FAIL, function(test) { + Base.consoleLog(indent() + color('fail', ' %d) %s'), ++n, test.title); + }); + + runner.once(EVENT_RUN_END, self.epilogue.bind(self)); } /** * Inherit from `Base.prototype`. */ +inherits(Spec, Base); -Spec.prototype = new Base; -Spec.prototype.constructor = Spec; - - -}); // module: reporters/spec.js - -require.register("reporters/tap.js", function(module, exports, require){ +Spec.description = 'hierarchical & verbose [default]'; +},{"../runner":34,"../utils":38,"./base":17}],31:[function(require,module,exports){ +(function (process){ +'use strict'; +/** + * @module TAP + */ /** * Module dependencies. */ -var Base = require('./base') - , cursor = Base.cursor - , color = Base.color; +var util = require('util'); +var Base = require('./base'); +var constants = require('../runner').constants; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; +var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING; +var EVENT_TEST_END = constants.EVENT_TEST_END; +var inherits = require('../utils').inherits; +var sprintf = util.format; /** * Expose `TAP`. @@ -3176,144 +4595,305 @@ var Base = require('./base') exports = module.exports = TAP; /** - * Initialize a new `TAP` reporter. + * Constructs a new `TAP` reporter instance. * - * @param {Runner} runner - * @api public + * @public + * @class + * @memberof Mocha.reporters + * @extends Mocha.reporters.Base + * @param {Runner} runner - Instance triggers reporter actions. + * @param {Object} [options] - runner options */ +function TAP(runner, options) { + Base.call(this, runner, options); -function TAP(runner) { - Base.call(this, runner); + var self = this; + var n = 1; - var self = this - , stats = this.stats - , n = 1; + var tapVersion = '12'; + if (options && options.reporterOptions) { + if (options.reporterOptions.tapVersion) { + tapVersion = options.reporterOptions.tapVersion.toString(); + } + } - runner.on('start', function(){ - var total = runner.grepTotal(runner.suite); - console.log('%d..%d', 1, total); + this._producer = createProducer(tapVersion); + + runner.once(EVENT_RUN_BEGIN, function() { + var ntests = runner.grepTotal(runner.suite); + self._producer.writeVersion(); + self._producer.writePlan(ntests); }); - runner.on('test end', function(){ + runner.on(EVENT_TEST_END, function() { ++n; }); - runner.on('pending', function(test){ - console.log('ok %d %s # SKIP -', n, title(test)); + runner.on(EVENT_TEST_PENDING, function(test) { + self._producer.writePending(n, test); }); - runner.on('pass', function(test){ - console.log('ok %d %s', n, title(test)); + runner.on(EVENT_TEST_PASS, function(test) { + self._producer.writePass(n, test); }); - runner.on('fail', function(test, err){ - console.log('not ok %d %s', n, title(test)); - console.log(err.stack.replace(/^/gm, ' ')); + runner.on(EVENT_TEST_FAIL, function(test, err) { + self._producer.writeFail(n, test, err); + }); + + runner.once(EVENT_RUN_END, function() { + self._producer.writeEpilogue(runner.stats); }); } /** - * Return a TAP-safe title of `test` - * - * @param {Object} test - * @return {String} - * @api private + * Inherit from `Base.prototype`. */ +inherits(TAP, Base); +/** + * Returns a TAP-safe title of `test`. + * + * @private + * @param {Test} test - Test instance. + * @return {String} title with any hash character removed + */ function title(test) { return test.fullTitle().replace(/#/g, ''); } -}); // module: reporters/tap.js +/** + * Writes newline-terminated formatted string to reporter output stream. + * + * @private + * @param {string} format - `printf`-like format string + * @param {...*} [varArgs] - Format string arguments + */ +function println(format, varArgs) { + var vargs = Array.from(arguments); + vargs[0] += '\n'; + process.stdout.write(sprintf.apply(null, vargs)); +} -require.register("reporters/teamcity.js", function(module, exports, require){ +/** + * Returns a `tapVersion`-appropriate TAP producer instance, if possible. + * + * @private + * @param {string} tapVersion - Version of TAP specification to produce. + * @returns {TAPProducer} specification-appropriate instance + * @throws {Error} if specification version has no associated producer. + */ +function createProducer(tapVersion) { + var producers = { + '12': new TAP12Producer(), + '13': new TAP13Producer() + }; + var producer = producers[tapVersion]; + if (!producer) { + throw new Error( + 'invalid or unsupported TAP version: ' + JSON.stringify(tapVersion) + ); + } + + return producer; +} + +/** + * @summary + * Constructs a new TAPProducer. + * + * @description + * Only to be used as an abstract base class. + * + * @private + * @constructor + */ +function TAPProducer() {} + +/** + * Writes the TAP version to reporter output stream. + * + * @abstract + */ +TAPProducer.prototype.writeVersion = function() {}; + +/** + * Writes the plan to reporter output stream. + * + * @abstract + * @param {number} ntests - Number of tests that are planned to run. + */ +TAPProducer.prototype.writePlan = function(ntests) { + println('%d..%d', 1, ntests); +}; + +/** + * Writes that test passed to reporter output stream. + * + * @abstract + * @param {number} n - Index of test that passed. + * @param {Test} test - Instance containing test information. + */ +TAPProducer.prototype.writePass = function(n, test) { + println('ok %d %s', n, title(test)); +}; + +/** + * Writes that test was skipped to reporter output stream. + * + * @abstract + * @param {number} n - Index of test that was skipped. + * @param {Test} test - Instance containing test information. + */ +TAPProducer.prototype.writePending = function(n, test) { + println('ok %d %s # SKIP -', n, title(test)); +}; + +/** + * Writes that test failed to reporter output stream. + * + * @abstract + * @param {number} n - Index of test that failed. + * @param {Test} test - Instance containing test information. + * @param {Error} err - Reason the test failed. + */ +TAPProducer.prototype.writeFail = function(n, test, err) { + println('not ok %d %s', n, title(test)); +}; + +/** + * Writes the summary epilogue to reporter output stream. + * + * @abstract + * @param {Object} stats - Object containing run statistics. + */ +TAPProducer.prototype.writeEpilogue = function(stats) { + // :TBD: Why is this not counting pending tests? + println('# tests ' + (stats.passes + stats.failures)); + println('# pass ' + stats.passes); + // :TBD: Why are we not showing pending results? + println('# fail ' + stats.failures); +}; + +/** + * @summary + * Constructs a new TAP12Producer. + * + * @description + * Produces output conforming to the TAP12 specification. + * + * @private + * @constructor + * @extends TAPProducer + * @see {@link https://testanything.org/tap-specification.html|Specification} + */ +function TAP12Producer() { + /** + * Writes that test failed to reporter output stream, with error formatting. + * @override + */ + this.writeFail = function(n, test, err) { + TAPProducer.prototype.writeFail.call(this, n, test, err); + if (err.message) { + println(err.message.replace(/^/gm, ' ')); + } + if (err.stack) { + println(err.stack.replace(/^/gm, ' ')); + } + }; +} + +/** + * Inherit from `TAPProducer.prototype`. + */ +inherits(TAP12Producer, TAPProducer); + +/** + * @summary + * Constructs a new TAP13Producer. + * + * @description + * Produces output conforming to the TAP13 specification. + * + * @private + * @constructor + * @extends TAPProducer + * @see {@link https://testanything.org/tap-version-13-specification.html|Specification} + */ +function TAP13Producer() { + /** + * Writes the TAP version to reporter output stream. + * @override + */ + this.writeVersion = function() { + println('TAP version 13'); + }; + + /** + * Writes that test failed to reporter output stream, with error formatting. + * @override + */ + this.writeFail = function(n, test, err) { + TAPProducer.prototype.writeFail.call(this, n, test, err); + var emitYamlBlock = err.message != null || err.stack != null; + if (emitYamlBlock) { + println(indent(1) + '---'); + if (err.message) { + println(indent(2) + 'message: |-'); + println(err.message.replace(/^/gm, indent(3))); + } + if (err.stack) { + println(indent(2) + 'stack: |-'); + println(err.stack.replace(/^/gm, indent(3))); + } + println(indent(1) + '...'); + } + }; + + function indent(level) { + return Array(level + 1).join(' '); + } +} + +/** + * Inherit from `TAPProducer.prototype`. + */ +inherits(TAP13Producer, TAPProducer); + +TAP.description = 'TAP-compatible output'; + +}).call(this,require('_process')) +},{"../runner":34,"../utils":38,"./base":17,"_process":69,"util":89}],32:[function(require,module,exports){ +(function (process,global){ +'use strict'; +/** + * @module XUnit + */ /** * Module dependencies. */ var Base = require('./base'); - -/** - * Expose `Teamcity`. - */ - -exports = module.exports = Teamcity; - -/** - * Initialize a new `Teamcity` reporter. - * - * @param {Runner} runner - * @api public - */ - -function Teamcity(runner) { - Base.call(this, runner); - var stats = this.stats; - - runner.on('start', function() { - console.log("##teamcity[testSuiteStarted name='mocha.suite']"); - }); - - runner.on('test', function(test) { - console.log("##teamcity[testStarted name='" + escape(test.fullTitle()) + "']"); - }); - - runner.on('fail', function(test, err) { - console.log("##teamcity[testFailed name='" + escape(test.fullTitle()) + "' message='" + escape(err.message) + "']"); - }); - - runner.on('pending', function(test) { - console.log("##teamcity[testIgnored name='" + escape(test.fullTitle()) + "' message='pending']"); - }); - - runner.on('test end', function(test) { - console.log("##teamcity[testFinished name='" + escape(test.fullTitle()) + "' duration='" + test.duration + "']"); - }); - - runner.on('end', function() { - console.log("##teamcity[testSuiteFinished name='mocha.suite' duration='" + stats.duration + "']"); - }); -} - -/** - * Escape the given `str`. - */ - -function escape(str) { - return str - .replace(/\|/g, "||") - .replace(/\n/g, "|n") - .replace(/\r/g, "|r") - .replace(/\[/g, "|[") - .replace(/\]/g, "|]") - .replace(/\u0085/g, "|x") - .replace(/\u2028/g, "|l") - .replace(/\u2029/g, "|p") - .replace(/'/g, "|'"); -} - -}); // module: reporters/teamcity.js - -require.register("reporters/xunit.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , utils = require('../utils') - , escape = utils.escape; +var utils = require('../utils'); +var fs = require('fs'); +var mkdirp = require('mkdirp'); +var path = require('path'); +var errors = require('../errors'); +var createUnsupportedError = errors.createUnsupportedError; +var constants = require('../runner').constants; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING; +var STATE_FAILED = require('../runnable').constants.STATE_FAILED; +var inherits = utils.inherits; +var escape = utils.escape; /** * Save timer references to avoid Sinon interfering (see GH-237). */ - -var Date = global.Date - , setTimeout = global.setTimeout - , setInterval = global.setInterval - , clearTimeout = global.clearTimeout - , clearInterval = global.clearInterval; +var Date = global.Date; /** * Expose `XUnit`. @@ -3322,382 +4902,848 @@ var Date = global.Date exports = module.exports = XUnit; /** - * Initialize a new `XUnit` reporter. + * Constructs a new `XUnit` reporter instance. * - * @param {Runner} runner - * @api public + * @public + * @class + * @memberof Mocha.reporters + * @extends Mocha.reporters.Base + * @param {Runner} runner - Instance triggers reporter actions. + * @param {Object} [options] - runner options */ +function XUnit(runner, options) { + Base.call(this, runner, options); -function XUnit(runner) { - Base.call(this, runner); - var stats = this.stats - , tests = [] - , self = this; + var stats = this.stats; + var tests = []; + var self = this; - runner.on('pass', function(test){ - tests.push(test); - }); - - runner.on('fail', function(test){ + // the name of the test suite, as it will appear in the resulting XML file + var suiteName; + + // the default name of the test suite if none is provided + var DEFAULT_SUITE_NAME = 'Mocha Tests'; + + if (options && options.reporterOptions) { + if (options.reporterOptions.output) { + if (!fs.createWriteStream) { + throw createUnsupportedError('file output not supported in browser'); + } + + mkdirp.sync(path.dirname(options.reporterOptions.output)); + self.fileStream = fs.createWriteStream(options.reporterOptions.output); + } + + // get the suite name from the reporter options (if provided) + suiteName = options.reporterOptions.suiteName; + } + + // fall back to the default suite name + suiteName = suiteName || DEFAULT_SUITE_NAME; + + runner.on(EVENT_TEST_PENDING, function(test) { tests.push(test); }); - runner.on('end', function(){ - console.log(tag('testsuite', { - name: 'Mocha Tests' - , tests: stats.tests - , failures: stats.failures - , errors: stats.failures - , skip: stats.tests - stats.failures - stats.passes - , timestamp: (new Date).toUTCString() - , time: stats.duration / 1000 - }, false)); + runner.on(EVENT_TEST_PASS, function(test) { + tests.push(test); + }); - tests.forEach(test); - console.log(''); + runner.on(EVENT_TEST_FAIL, function(test) { + tests.push(test); + }); + + runner.once(EVENT_RUN_END, function() { + self.write( + tag( + 'testsuite', + { + name: suiteName, + tests: stats.tests, + failures: 0, + errors: stats.failures, + skipped: stats.tests - stats.failures - stats.passes, + timestamp: new Date().toUTCString(), + time: stats.duration / 1000 || 0 + }, + false + ) + ); + + tests.forEach(function(t) { + self.test(t); + }); + + self.write(''); }); } /** * Inherit from `Base.prototype`. */ +inherits(XUnit, Base); -XUnit.prototype = new Base; -XUnit.prototype.constructor = XUnit; +/** + * Override done to close the stream (if it's a file). + * + * @param failures + * @param {Function} fn + */ +XUnit.prototype.done = function(failures, fn) { + if (this.fileStream) { + this.fileStream.end(function() { + fn(failures); + }); + } else { + fn(failures); + } +}; +/** + * Write out the given line. + * + * @param {string} line + */ +XUnit.prototype.write = function(line) { + if (this.fileStream) { + this.fileStream.write(line + '\n'); + } else if (typeof process === 'object' && process.stdout) { + process.stdout.write(line + '\n'); + } else { + Base.consoleLog(line); + } +}; /** * Output tag for the given `test.` + * + * @param {Test} test */ +XUnit.prototype.test = function(test) { + Base.useColors = false; -function test(test) { var attrs = { - classname: test.parent.fullTitle() - , name: test.title - , time: test.duration / 1000 + classname: test.parent.fullTitle(), + name: test.title, + time: test.duration / 1000 || 0 }; - if ('failed' == test.state) { + if (test.state === STATE_FAILED) { var err = test.err; - attrs.message = escape(err.message); - console.log(tag('testcase', attrs, false, tag('failure', attrs, false, cdata(err.stack)))); - } else if (test.pending) { - console.log(tag('testcase', attrs, false, tag('skipped', {}, true))); + var diff = + !Base.hideDiff && Base.showDiff(err) + ? '\n' + Base.generateDiff(err.actual, err.expected) + : ''; + this.write( + tag( + 'testcase', + attrs, + false, + tag( + 'failure', + {}, + false, + escape(err.message) + escape(diff) + '\n' + escape(err.stack) + ) + ) + ); + } else if (test.isPending()) { + this.write(tag('testcase', attrs, false, tag('skipped', {}, true))); } else { - console.log(tag('testcase', attrs, true) ); + this.write(tag('testcase', attrs, true)); } -} +}; /** * HTML tag helper. + * + * @param name + * @param attrs + * @param close + * @param content + * @return {string} */ - function tag(name, attrs, close, content) { - var end = close ? '/>' : '>' - , pairs = [] - , tag; + var end = close ? '/>' : '>'; + var pairs = []; + var tag; for (var key in attrs) { - pairs.push(key + '="' + escape(attrs[key]) + '"'); + if (Object.prototype.hasOwnProperty.call(attrs, key)) { + pairs.push(key + '="' + escape(attrs[key]) + '"'); + } } tag = '<' + name + (pairs.length ? ' ' + pairs.join(' ') : '') + end; - if (content) tag += content + ''; -} +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"../errors":6,"../runnable":33,"../runner":34,"../utils":38,"./base":17,"_process":69,"fs":42,"mkdirp":59,"path":42}],33:[function(require,module,exports){ +(function (global){ +'use strict'; -}); // module: reporters/xunit.js - -require.register("runnable.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var EventEmitter = require('browser/events').EventEmitter - , debug = require('browser/debug')('mocha:runnable'); +var EventEmitter = require('events').EventEmitter; +var Pending = require('./pending'); +var debug = require('debug')('mocha:runnable'); +var milliseconds = require('ms'); +var utils = require('./utils'); +var createInvalidExceptionError = require('./errors') + .createInvalidExceptionError; /** * Save timer references to avoid Sinon interfering (see GH-237). */ - -var Date = global.Date - , setTimeout = global.setTimeout - , setInterval = global.setInterval - , clearTimeout = global.clearTimeout - , clearInterval = global.clearInterval; - -/** - * Expose `Runnable`. - */ +var Date = global.Date; +var setTimeout = global.setTimeout; +var clearTimeout = global.clearTimeout; +var toString = Object.prototype.toString; module.exports = Runnable; /** * Initialize a new `Runnable` with the given `title` and callback `fn`. * + * @class + * @extends external:EventEmitter + * @public * @param {String} title * @param {Function} fn - * @api private */ - function Runnable(title, fn) { this.title = title; this.fn = fn; + this.body = (fn || '').toString(); this.async = fn && fn.length; - this.sync = ! this.async; + this.sync = !this.async; this._timeout = 2000; this._slow = 75; + this._enableTimeouts = true; this.timedOut = false; + this._retries = -1; + this._currentRetry = 0; + this.pending = false; } /** * Inherit from `EventEmitter.prototype`. */ - -Runnable.prototype = new EventEmitter; -Runnable.prototype.constructor = Runnable; - +utils.inherits(Runnable, EventEmitter); /** - * Set & get timeout `ms`. + * Get current timeout value in msecs. * - * @param {Number} ms - * @return {Runnable|Number} ms or self - * @api private + * @private + * @returns {number} current timeout threshold value */ +/** + * @summary + * Set timeout threshold value (msecs). + * + * @description + * A string argument can use shorthand (e.g., "2s") and will be converted. + * The value will be clamped to range [0, 2^31-1]. + * If clamped value matches either range endpoint, timeouts will be disabled. + * + * @private + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout#Maximum_delay_value} + * @param {number|string} ms - Timeout threshold value. + * @returns {Runnable} this + * @chainable + */ +Runnable.prototype.timeout = function(ms) { + if (!arguments.length) { + return this._timeout; + } + if (typeof ms === 'string') { + ms = milliseconds(ms); + } -Runnable.prototype.timeout = function(ms){ - if (0 == arguments.length) return this._timeout; + // Clamp to range + var INT_MAX = Math.pow(2, 31) - 1; + var range = [0, INT_MAX]; + ms = utils.clamp(ms, range); + + // see #1652 for reasoning + if (ms === range[0] || ms === range[1]) { + this._enableTimeouts = false; + } debug('timeout %d', ms); this._timeout = ms; - if (this.timer) this.resetTimeout(); + if (this.timer) { + this.resetTimeout(); + } return this; }; /** - * Set & get slow `ms`. + * Set or get slow `ms`. * - * @param {Number} ms - * @return {Runnable|Number} ms or self - * @api private + * @private + * @param {number|string} ms + * @return {Runnable|number} ms or Runnable instance. */ - -Runnable.prototype.slow = function(ms){ - if (0 === arguments.length) return this._slow; - debug('timeout %d', ms); +Runnable.prototype.slow = function(ms) { + if (!arguments.length || typeof ms === 'undefined') { + return this._slow; + } + if (typeof ms === 'string') { + ms = milliseconds(ms); + } + debug('slow %d', ms); this._slow = ms; return this; }; /** - * Return the full title generated by recursively - * concatenating the parent's full title. + * Set and get whether timeout is `enabled`. * - * @return {String} - * @api public + * @private + * @param {boolean} enabled + * @return {Runnable|boolean} enabled or Runnable instance. */ +Runnable.prototype.enableTimeouts = function(enabled) { + if (!arguments.length) { + return this._enableTimeouts; + } + debug('enableTimeouts %s', enabled); + this._enableTimeouts = enabled; + return this; +}; -Runnable.prototype.fullTitle = function(){ - return this.parent.fullTitle() + ' ' + this.title; +/** + * Halt and mark as pending. + * + * @memberof Mocha.Runnable + * @public + */ +Runnable.prototype.skip = function() { + this.pending = true; + throw new Pending('sync skip; aborting execution'); +}; + +/** + * Check if this runnable or its parent suite is marked as pending. + * + * @private + */ +Runnable.prototype.isPending = function() { + return this.pending || (this.parent && this.parent.isPending()); +}; + +/** + * Return `true` if this Runnable has failed. + * @return {boolean} + * @private + */ +Runnable.prototype.isFailed = function() { + return !this.isPending() && this.state === constants.STATE_FAILED; +}; + +/** + * Return `true` if this Runnable has passed. + * @return {boolean} + * @private + */ +Runnable.prototype.isPassed = function() { + return !this.isPending() && this.state === constants.STATE_PASSED; +}; + +/** + * Set or get number of retries. + * + * @private + */ +Runnable.prototype.retries = function(n) { + if (!arguments.length) { + return this._retries; + } + this._retries = n; +}; + +/** + * Set or get current retry + * + * @private + */ +Runnable.prototype.currentRetry = function(n) { + if (!arguments.length) { + return this._currentRetry; + } + this._currentRetry = n; +}; + +/** + * Return the full title generated by recursively concatenating the parent's + * full title. + * + * @memberof Mocha.Runnable + * @public + * @return {string} + */ +Runnable.prototype.fullTitle = function() { + return this.titlePath().join(' '); +}; + +/** + * Return the title path generated by concatenating the parent's title path with the title. + * + * @memberof Mocha.Runnable + * @public + * @return {string} + */ +Runnable.prototype.titlePath = function() { + return this.parent.titlePath().concat([this.title]); }; /** * Clear the timeout. * - * @api private + * @private */ - -Runnable.prototype.clearTimeout = function(){ +Runnable.prototype.clearTimeout = function() { clearTimeout(this.timer); }; -/** - * Inspect the runnable void of private properties. - * - * @return {String} - * @api private - */ - -Runnable.prototype.inspect = function(){ - return JSON.stringify(this, function(key, val){ - if ('_' == key[0]) return; - if ('parent' == key) return '#'; - if ('ctx' == key) return '#'; - return val; - }, 2); -}; - /** * Reset the timeout. * - * @api private + * @private */ +Runnable.prototype.resetTimeout = function() { + var self = this; + var ms = this.timeout() || 1e9; -Runnable.prototype.resetTimeout = function(){ - var self = this - , ms = this.timeout(); - - this.clearTimeout(); - if (ms) { - this.timer = setTimeout(function(){ - self.callback(new Error('timeout of ' + ms + 'ms exceeded')); - self.timedOut = true; - }, ms); + if (!this._enableTimeouts) { + return; } + this.clearTimeout(); + this.timer = setTimeout(function() { + if (!self._enableTimeouts) { + return; + } + self.callback(self._timeoutError(ms)); + self.timedOut = true; + }, ms); +}; + +/** + * Set or get a list of whitelisted globals for this test run. + * + * @private + * @param {string[]} globals + */ +Runnable.prototype.globals = function(globals) { + if (!arguments.length) { + return this._allowedGlobals; + } + this._allowedGlobals = globals; }; /** * Run the test and invoke `fn(err)`. * * @param {Function} fn - * @api private + * @private */ +Runnable.prototype.run = function(fn) { + var self = this; + var start = new Date(); + var ctx = this.ctx; + var finished; + var emitted; -Runnable.prototype.run = function(fn){ - var self = this - , ms = this.timeout() - , start = new Date - , ctx = this.ctx - , finished - , emitted; - - if (ctx) ctx.runnable(this); - - // timeout - if (this.async) { - if (ms) { - this.timer = setTimeout(function(){ - done(new Error('timeout of ' + ms + 'ms exceeded')); - self.timedOut = true; - }, ms); - } + // Sometimes the ctx exists, but it is not runnable + if (ctx && ctx.runnable) { + ctx.runnable(this); } // called multiple times function multiple(err) { - if (emitted) return; + if (emitted) { + return; + } emitted = true; - self.emit('error', err || new Error('done() called multiple times')); + var msg = 'done() called multiple times'; + if (err && err.message) { + err.message += " (and Mocha's " + msg + ')'; + self.emit('error', err); + } else { + self.emit('error', new Error(msg)); + } } // finished function done(err) { - if (self.timedOut) return; - if (finished) return multiple(err); + var ms = self.timeout(); + if (self.timedOut) { + return; + } + + if (finished) { + return multiple(err); + } + self.clearTimeout(); - self.duration = new Date - start; + self.duration = new Date() - start; finished = true; + if (!err && self.duration > ms && self._enableTimeouts) { + err = self._timeoutError(ms); + } fn(err); } - // for .resetTimeout() + // for .resetTimeout() and Runner#uncaught() this.callback = done; - // async + if (this.fn && typeof this.fn.call !== 'function') { + done( + new TypeError( + 'A runnable must be passed a function as its second argument.' + ) + ); + return; + } + + // explicit async with `done` argument if (this.async) { + this.resetTimeout(); + + // allows skip() to be used in an explicit async context + this.skip = function asyncSkip() { + this.pending = true; + done(); + // halt execution, the uncaught handler will ignore the failure. + throw new Pending('async skip; aborting execution'); + }; + try { - this.fn.call(ctx, function(err){ - if (err instanceof Error) return done(err); - if (null != err) return done(new Error('done() invoked with non-Error: ' + err)); - done(); - }); + callFnAsync(this.fn); } catch (err) { - done(err); + // handles async runnables which actually run synchronously + emitted = true; + if (err instanceof Pending) { + return; // done() is already called in this.skip() + } else if (this.allowUncaught) { + throw err; + } + done(Runnable.toValueOrError(err)); } return; } - - // sync + + // sync or promise-returning try { - if (!this.pending) this.fn.call(ctx); - this.duration = new Date - start; - fn(); + if (this.isPending()) { + done(); + } else { + callFn(this.fn); + } } catch (err) { - fn(err); + emitted = true; + if (err instanceof Pending) { + return done(); + } else if (this.allowUncaught) { + throw err; + } + done(Runnable.toValueOrError(err)); + } + + function callFn(fn) { + var result = fn.call(ctx); + if (result && typeof result.then === 'function') { + self.resetTimeout(); + result.then( + function() { + done(); + // Return null so libraries like bluebird do not warn about + // subsequently constructed Promises. + return null; + }, + function(reason) { + done(reason || new Error('Promise rejected with no or falsy reason')); + } + ); + } else { + if (self.asyncOnly) { + return done( + new Error( + '--async-only option in use without declaring `done()` or returning a promise' + ) + ); + } + + done(); + } + } + + function callFnAsync(fn) { + var result = fn.call(ctx, function(err) { + if (err instanceof Error || toString.call(err) === '[object Error]') { + return done(err); + } + if (err) { + if (Object.prototype.toString.call(err) === '[object Object]') { + return done( + new Error('done() invoked with non-Error: ' + JSON.stringify(err)) + ); + } + return done(new Error('done() invoked with non-Error: ' + err)); + } + if (result && utils.isPromise(result)) { + return done( + new Error( + 'Resolution method is overspecified. Specify a callback *or* return a Promise; not both.' + ) + ); + } + + done(); + }); } }; -}); // module: runnable.js +/** + * Instantiates a "timeout" error + * + * @param {number} ms - Timeout (in milliseconds) + * @returns {Error} a "timeout" error + * @private + */ +Runnable.prototype._timeoutError = function(ms) { + var msg = + 'Timeout of ' + + ms + + 'ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.'; + if (this.file) { + msg += ' (' + this.file + ')'; + } + return new Error(msg); +}; -require.register("runner.js", function(module, exports, require){ +var constants = utils.defineConstants( + /** + * {@link Runnable}-related constants. + * @public + * @memberof Runnable + * @readonly + * @static + * @alias constants + * @enum {string} + */ + { + /** + * Value of `state` prop when a `Runnable` has failed + */ + STATE_FAILED: 'failed', + /** + * Value of `state` prop when a `Runnable` has passed + */ + STATE_PASSED: 'passed' + } +); + +/** + * Given `value`, return identity if truthy, otherwise create an "invalid exception" error and return that. + * @param {*} [value] - Value to return, if present + * @returns {*|Error} `value`, otherwise an `Error` + * @private + */ +Runnable.toValueOrError = function(value) { + return ( + value || + createInvalidExceptionError( + 'Runnable failed with falsy or undefined exception. Please throw an Error instead.', + value + ) + ); +}; + +Runnable.constants = constants; + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./errors":6,"./pending":16,"./utils":38,"debug":45,"events":50,"ms":60}],34:[function(require,module,exports){ +(function (process,global){ +'use strict'; /** * Module dependencies. */ - -var EventEmitter = require('browser/events').EventEmitter - , debug = require('browser/debug')('mocha:runner') - , Test = require('./test') - , utils = require('./utils') - , filter = utils.filter - , keys = utils.keys - , noop = function(){}; +var util = require('util'); +var EventEmitter = require('events').EventEmitter; +var Pending = require('./pending'); +var utils = require('./utils'); +var inherits = utils.inherits; +var debug = require('debug')('mocha:runner'); +var Runnable = require('./runnable'); +var Suite = require('./suite'); +var HOOK_TYPE_BEFORE_EACH = Suite.constants.HOOK_TYPE_BEFORE_EACH; +var HOOK_TYPE_AFTER_EACH = Suite.constants.HOOK_TYPE_AFTER_EACH; +var HOOK_TYPE_AFTER_ALL = Suite.constants.HOOK_TYPE_AFTER_ALL; +var HOOK_TYPE_BEFORE_ALL = Suite.constants.HOOK_TYPE_BEFORE_ALL; +var EVENT_ROOT_SUITE_RUN = Suite.constants.EVENT_ROOT_SUITE_RUN; +var STATE_FAILED = Runnable.constants.STATE_FAILED; +var STATE_PASSED = Runnable.constants.STATE_PASSED; +var dQuote = utils.dQuote; +var sQuote = utils.sQuote; +var stackFilter = utils.stackTraceFilter(); +var stringify = utils.stringify; +var type = utils.type; +var errors = require('./errors'); +var createInvalidExceptionError = errors.createInvalidExceptionError; +var createUnsupportedError = errors.createUnsupportedError; /** - * Expose `Runner`. + * Non-enumerable globals. + * @readonly */ +var globals = [ + 'setTimeout', + 'clearTimeout', + 'setInterval', + 'clearInterval', + 'XMLHttpRequest', + 'Date', + 'setImmediate', + 'clearImmediate' +]; + +var constants = utils.defineConstants( + /** + * {@link Runner}-related constants. + * @public + * @memberof Runner + * @readonly + * @alias constants + * @static + * @enum {string} + */ + { + /** + * Emitted when {@link Hook} execution begins + */ + EVENT_HOOK_BEGIN: 'hook', + /** + * Emitted when {@link Hook} execution ends + */ + EVENT_HOOK_END: 'hook end', + /** + * Emitted when Root {@link Suite} execution begins (all files have been parsed and hooks/tests are ready for execution) + */ + EVENT_RUN_BEGIN: 'start', + /** + * Emitted when Root {@link Suite} execution has been delayed via `delay` option + */ + EVENT_DELAY_BEGIN: 'waiting', + /** + * Emitted when delayed Root {@link Suite} execution is triggered by user via `global.run()` + */ + EVENT_DELAY_END: 'ready', + /** + * Emitted when Root {@link Suite} execution ends + */ + EVENT_RUN_END: 'end', + /** + * Emitted when {@link Suite} execution begins + */ + EVENT_SUITE_BEGIN: 'suite', + /** + * Emitted when {@link Suite} execution ends + */ + EVENT_SUITE_END: 'suite end', + /** + * Emitted when {@link Test} execution begins + */ + EVENT_TEST_BEGIN: 'test', + /** + * Emitted when {@link Test} execution ends + */ + EVENT_TEST_END: 'test end', + /** + * Emitted when {@link Test} execution fails + */ + EVENT_TEST_FAIL: 'fail', + /** + * Emitted when {@link Test} execution succeeds + */ + EVENT_TEST_PASS: 'pass', + /** + * Emitted when {@link Test} becomes pending + */ + EVENT_TEST_PENDING: 'pending', + /** + * Emitted when {@link Test} execution has failed, but will retry + */ + EVENT_TEST_RETRY: 'retry' + } +); module.exports = Runner; /** - * Initialize a `Runner` for the given `suite`. + * Initialize a `Runner` at the Root {@link Suite}, which represents a hierarchy of {@link Suite|Suites} and {@link Test|Tests}. * - * Events: - * - * - `start` execution started - * - `end` execution complete - * - `suite` (suite) test suite execution started - * - `suite end` (suite) all tests (and sub-suites) have finished - * - `test` (test) test execution started - * - `test end` (test) test completed - * - `hook` (hook) hook execution started - * - `hook end` (hook) hook complete - * - `pass` (test) test passed - * - `fail` (test, err) test failed - * - * @api public + * @extends external:EventEmitter + * @public + * @class + * @param {Suite} suite Root suite + * @param {boolean} [delay] Whether or not to delay execution of root suite + * until ready. */ - -function Runner(suite) { +function Runner(suite, delay) { var self = this; this._globals = []; + this._abort = false; + this._delay = delay; this.suite = suite; + this.started = false; this.total = suite.total(); this.failures = 0; - this.on('test end', function(test){ self.checkGlobals(test); }); - this.on('hook end', function(hook){ self.checkGlobals(hook); }); - this.grep(/.*/); - this.globals(utils.keys(global).concat(['errno'])); + this.on(constants.EVENT_TEST_END, function(test) { + if (test.type === 'test' && test.retriedTest() && test.parent) { + var idx = + test.parent.tests && test.parent.tests.indexOf(test.retriedTest()); + if (idx > -1) test.parent.tests[idx] = test; + } + self.checkGlobals(test); + }); + this.on(constants.EVENT_HOOK_END, function(hook) { + self.checkGlobals(hook); + }); + this._defaultGrep = /.*/; + this.grep(this._defaultGrep); + this.globals(this.globalProps()); } +/** + * Wrapper for setImmediate, process.nextTick, or browser polyfill. + * + * @param {Function} fn + * @private + */ +Runner.immediately = global.setImmediate || process.nextTick; + /** * Inherit from `EventEmitter.prototype`. */ - -Runner.prototype = new EventEmitter; -Runner.prototype.constructor = Runner; - +inherits(Runner, EventEmitter); /** * Run tests with full titles matching `re`. Updates runner.total * with number of tests matched. * + * @public + * @memberof Runner * @param {RegExp} re - * @param {Boolean} invert - * @return {Runner} for chaining - * @api public + * @param {boolean} invert + * @return {Runner} Runner instance. */ - -Runner.prototype.grep = function(re, invert){ +Runner.prototype.grep = function(re, invert) { debug('grep %s', re); this._grep = re; this._invert = invert; @@ -3709,156 +5755,263 @@ Runner.prototype.grep = function(re, invert){ * Returns the number of tests matching the grep search for the * given suite. * + * @memberof Runner + * @public * @param {Suite} suite - * @return {Number} - * @api public + * @return {number} */ - Runner.prototype.grepTotal = function(suite) { var self = this; var total = 0; - suite.eachTest(function(test){ + suite.eachTest(function(test) { var match = self._grep.test(test.fullTitle()); - if (self._invert) match = !match; - if (match) total++; + if (self._invert) { + match = !match; + } + if (match) { + total++; + } }); return total; }; +/** + * Return a list of global properties. + * + * @return {Array} + * @private + */ +Runner.prototype.globalProps = function() { + var props = Object.keys(global); + + // non-enumerables + for (var i = 0; i < globals.length; ++i) { + if (~props.indexOf(globals[i])) { + continue; + } + props.push(globals[i]); + } + + return props; +}; + /** * Allow the given `arr` of globals. * + * @public + * @memberof Runner * @param {Array} arr - * @return {Runner} for chaining - * @api public + * @return {Runner} Runner instance. */ - -Runner.prototype.globals = function(arr){ - if (0 == arguments.length) return this._globals; +Runner.prototype.globals = function(arr) { + if (!arguments.length) { + return this._globals; + } debug('globals %j', arr); - utils.forEach(arr, function(arr){ - this._globals.push(arr); - }, this); + this._globals = this._globals.concat(arr); return this; }; /** * Check for global variable leaks. * - * @api private + * @private */ - -Runner.prototype.checkGlobals = function(test){ - if (this.ignoreLeaks) return; +Runner.prototype.checkGlobals = function(test) { + if (!this.checkLeaks) { + return; + } var ok = this._globals; - var globals = keys(global); - var isNode = process.kill; + + var globals = this.globalProps(); var leaks; - // check length - 2 ('errno' and 'location' globals) - if (isNode && 1 == ok.length - globals.length) return - else if (2 == ok.length - globals.length) return; + if (test) { + ok = ok.concat(test._allowedGlobals || []); + } + + if (this.prevGlobalsLength === globals.length) { + return; + } + this.prevGlobalsLength = globals.length; leaks = filterLeaks(ok, globals); this._globals = this._globals.concat(leaks); - if (leaks.length > 1) { - this.fail(test, new Error('global leaks detected: ' + leaks.join(', ') + '')); - } else if (leaks.length) { - this.fail(test, new Error('global leak detected: ' + leaks[0])); + if (leaks.length) { + var msg = 'global leak(s) detected: %s'; + var error = new Error(util.format(msg, leaks.map(sQuote).join(', '))); + this.fail(test, error); } }; /** * Fail the given `test`. * + * @private * @param {Test} test * @param {Error} err - * @api private */ - -Runner.prototype.fail = function(test, err){ - ++this.failures; - test.state = 'failed'; - if ('string' == typeof err) { - err = new Error('the string "' + err + '" was thrown, throw an Error :)'); +Runner.prototype.fail = function(test, err) { + if (test.isPending()) { + return; } - this.emit('fail', test, err); + + ++this.failures; + test.state = STATE_FAILED; + + if (!isError(err)) { + err = thrown2Error(err); + } + + try { + err.stack = + this.fullStackTrace || !err.stack ? err.stack : stackFilter(err.stack); + } catch (ignore) { + // some environments do not take kindly to monkeying with the stack + } + + this.emit(constants.EVENT_TEST_FAIL, test, err); }; /** * Fail the given `hook` with `err`. * - * Hook failures (currently) hard-end due - * to that fact that a failing hook will - * surely cause subsequent tests to fail, - * causing jumbled reporting. + * Hook failures work in the following pattern: + * - If bail, run corresponding `after each` and `after` hooks, + * then exit + * - Failed `before` hook skips all tests in a suite and subsuites, + * but jumps to corresponding `after` hook + * - Failed `before each` hook skips remaining tests in a + * suite and jumps to corresponding `after each` hook, + * which is run only once + * - Failed `after` hook does not alter execution order + * - Failed `after each` hook skips remaining tests in a + * suite and subsuites, but executes other `after each` + * hooks * + * @private * @param {Hook} hook * @param {Error} err - * @api private */ +Runner.prototype.failHook = function(hook, err) { + hook.originalTitle = hook.originalTitle || hook.title; + if (hook.ctx && hook.ctx.currentTest) { + hook.title = + hook.originalTitle + ' for ' + dQuote(hook.ctx.currentTest.title); + } else { + var parentTitle; + if (hook.parent.title) { + parentTitle = hook.parent.title; + } else { + parentTitle = hook.parent.root ? '{root}' : ''; + } + hook.title = hook.originalTitle + ' in ' + dQuote(parentTitle); + } -Runner.prototype.failHook = function(hook, err){ this.fail(hook, err); - this.emit('end'); }; /** * Run hook `name` callbacks and then invoke `fn()`. * - * @param {String} name - * @param {Function} function - * @api private + * @private + * @param {string} name + * @param {Function} fn */ -Runner.prototype.hook = function(name, fn){ - var suite = this.suite - , hooks = suite['_' + name] - , self = this - , timer; +Runner.prototype.hook = function(name, fn) { + var suite = this.suite; + var hooks = suite.getHooks(name); + var self = this; function next(i) { var hook = hooks[i]; - if (!hook) return fn(); + if (!hook) { + return fn(); + } self.currentRunnable = hook; - self.emit('hook', hook); + if (name === HOOK_TYPE_BEFORE_ALL) { + hook.ctx.currentTest = hook.parent.tests[0]; + } else if (name === HOOK_TYPE_AFTER_ALL) { + hook.ctx.currentTest = hook.parent.tests[hook.parent.tests.length - 1]; + } else { + hook.ctx.currentTest = self.test; + } - hook.on('error', function(err){ - self.failHook(hook, err); - }); + hook.allowUncaught = self.allowUncaught; - hook.run(function(err){ - hook.removeAllListeners('error'); + self.emit(constants.EVENT_HOOK_BEGIN, hook); + + if (!hook.listeners('error').length) { + hook.on('error', function(err) { + self.failHook(hook, err); + }); + } + + hook.run(function(err) { var testError = hook.error(); - if (testError) self.fail(self.test, testError); - if (err) return self.failHook(hook, err); - self.emit('hook end', hook); + if (testError) { + self.fail(self.test, testError); + } + // conditional skip + if (hook.pending) { + if (name === HOOK_TYPE_AFTER_EACH) { + // TODO define and implement use case + if (self.test) { + self.test.pending = true; + } + } else if (name === HOOK_TYPE_BEFORE_EACH) { + if (self.test) { + self.test.pending = true; + } + self.emit(constants.EVENT_HOOK_END, hook); + hook.pending = false; // activates hook for next test + return fn(new Error('abort hookDown')); + } else if (name === HOOK_TYPE_BEFORE_ALL) { + suite.tests.forEach(function(test) { + test.pending = true; + }); + suite.suites.forEach(function(suite) { + suite.pending = true; + }); + } else { + hook.pending = false; + var errForbid = createUnsupportedError('`this.skip` forbidden'); + self.failHook(hook, errForbid); + return fn(errForbid); + } + } else if (err) { + self.failHook(hook, err); + // stop executing hooks, notify callee of hook err + return fn(err); + } + self.emit(constants.EVENT_HOOK_END, hook); + delete hook.ctx.currentTest; next(++i); }); } - process.nextTick(function(){ + Runner.immediately(function() { next(0); }); }; /** * Run hook `name` for the given array of `suites` - * in order, and callback `fn(err)`. + * in order, and callback `fn(err, errSuite)`. * - * @param {String} name + * @private + * @param {string} name * @param {Array} suites * @param {Function} fn - * @api private */ - -Runner.prototype.hooks = function(name, suites, fn){ - var self = this - , orig = this.suite; +Runner.prototype.hooks = function(name, suites, fn) { + var self = this; + var orig = this.suite; function next(suite) { self.suite = suite; @@ -3868,10 +6021,11 @@ Runner.prototype.hooks = function(name, suites, fn){ return fn(); } - self.hook(name, function(err){ + self.hook(name, function(err) { if (err) { + var errSuite = self.suite; self.suite = orig; - return fn(err); + return fn(err, errSuite); } next(suites.pop()); @@ -3886,10 +6040,9 @@ Runner.prototype.hooks = function(name, suites, fn){ * * @param {String} name * @param {Function} fn - * @api private + * @private */ - -Runner.prototype.hookUp = function(name, fn){ +Runner.prototype.hookUp = function(name, fn) { var suites = [this.suite].concat(this.parents()).reverse(); this.hooks(name, suites, fn); }; @@ -3899,10 +6052,9 @@ Runner.prototype.hookUp = function(name, fn){ * * @param {String} name * @param {Function} fn - * @api private + * @private */ - -Runner.prototype.hookDown = function(name, fn){ +Runner.prototype.hookDown = function(name, fn) { var suites = [this.suite].concat(this.parents()); this.hooks(name, suites, fn); }; @@ -3912,13 +6064,15 @@ Runner.prototype.hookDown = function(name, fn){ * closest to furthest. * * @return {Array} - * @api private + * @private */ - -Runner.prototype.parents = function(){ - var suite = this.suite - , suites = []; - while (suite = suite.parent) suites.push(suite); +Runner.prototype.parents = function() { + var suite = this.suite; + var suites = []; + while (suite.parent) { + suite = suite.parent; + suites.push(suite); + } return suites; }; @@ -3926,17 +6080,35 @@ Runner.prototype.parents = function(){ * Run the current test and callback `fn(err)`. * * @param {Function} fn - * @api private + * @private */ +Runner.prototype.runTest = function(fn) { + var self = this; + var test = this.test; -Runner.prototype.runTest = function(fn){ - var test = this.test - , self = this; + if (!test) { + return; + } + var suite = this.parents().reverse()[0] || this.suite; + if (this.forbidOnly && suite.hasOnly()) { + fn(new Error('`.only` forbidden')); + return; + } + if (this.asyncOnly) { + test.asyncOnly = true; + } + test.on('error', function(err) { + if (err instanceof Pending) { + return; + } + self.fail(test, err); + }); + if (this.allowUncaught) { + test.allowUncaught = true; + return test.run(fn); + } try { - test.on('error', function(err){ - self.fail(test, err); - }); test.run(fn); } catch (err) { fn(err); @@ -3944,203 +6116,596 @@ Runner.prototype.runTest = function(fn){ }; /** - * Run tests in the given `suite` and invoke - * the callback `fn()` when complete. + * Run tests in the given `suite` and invoke the callback `fn()` when complete. * + * @private * @param {Suite} suite * @param {Function} fn - * @api private */ +Runner.prototype.runTests = function(suite, fn) { + var self = this; + var tests = suite.tests.slice(); + var test; -Runner.prototype.runTests = function(suite, fn){ - var self = this - , tests = suite.tests - , test; + function hookErr(_, errSuite, after) { + // before/after Each hook for errSuite failed: + var orig = self.suite; - function next(err) { + // for failed 'after each' hook start from errSuite parent, + // otherwise start from errSuite itself + self.suite = after ? errSuite.parent : errSuite; + + if (self.suite) { + // call hookUp afterEach + self.hookUp(HOOK_TYPE_AFTER_EACH, function(err2, errSuite2) { + self.suite = orig; + // some hooks may fail even now + if (err2) { + return hookErr(err2, errSuite2, true); + } + // report error suite + fn(errSuite); + }); + } else { + // there is no need calling other 'after each' hooks + self.suite = orig; + fn(errSuite); + } + } + + function next(err, errSuite) { // if we bail after first err - if (self.failures && suite._bail) return fn(); + if (self.failures && suite._bail) { + tests = []; + } + + if (self._abort) { + return fn(); + } + + if (err) { + return hookErr(err, errSuite, true); + } // next test test = tests.shift(); // all done - if (!test) return fn(); + if (!test) { + return fn(); + } // grep var match = self._grep.test(test.fullTitle()); - if (self._invert) match = !match; - if (!match) return next(); + if (self._invert) { + match = !match; + } + if (!match) { + // Run immediately only if we have defined a grep. When we + // define a grep โ€” It can cause maximum callstack error if + // the grep is doing a large recursive loop by neglecting + // all tests. The run immediately function also comes with + // a performance cost. So we don't want to run immediately + // if we run the whole test suite, because running the whole + // test suite don't do any immediate recursive loops. Thus, + // allowing a JS runtime to breathe. + if (self._grep !== self._defaultGrep) { + Runner.immediately(next); + } else { + next(); + } + return; + } - // pending - if (test.pending) { - self.emit('pending', test); - self.emit('test end', test); + // static skip, no hooks are executed + if (test.isPending()) { + if (self.forbidPending) { + test.isPending = alwaysFalse; + self.fail(test, new Error('Pending test forbidden')); + delete test.isPending; + } else { + self.emit(constants.EVENT_TEST_PENDING, test); + } + self.emit(constants.EVENT_TEST_END, test); return next(); } // execute test and hook(s) - self.emit('test', self.test = test); - self.hookDown('beforeEach', function(){ + self.emit(constants.EVENT_TEST_BEGIN, (self.test = test)); + self.hookDown(HOOK_TYPE_BEFORE_EACH, function(err, errSuite) { + // conditional skip within beforeEach + if (test.isPending()) { + if (self.forbidPending) { + test.isPending = alwaysFalse; + self.fail(test, new Error('Pending test forbidden')); + delete test.isPending; + } else { + self.emit(constants.EVENT_TEST_PENDING, test); + } + self.emit(constants.EVENT_TEST_END, test); + // skip inner afterEach hooks below errSuite level + var origSuite = self.suite; + self.suite = errSuite || self.suite; + return self.hookUp(HOOK_TYPE_AFTER_EACH, function(e, eSuite) { + self.suite = origSuite; + next(e, eSuite); + }); + } + if (err) { + return hookErr(err, errSuite, false); + } self.currentRunnable = self.test; - self.runTest(function(err){ + self.runTest(function(err) { test = self.test; + // conditional skip within it + if (test.pending) { + if (self.forbidPending) { + test.isPending = alwaysFalse; + self.fail(test, new Error('Pending test forbidden')); + delete test.isPending; + } else { + self.emit(constants.EVENT_TEST_PENDING, test); + } + self.emit(constants.EVENT_TEST_END, test); + return self.hookUp(HOOK_TYPE_AFTER_EACH, next); + } else if (err) { + var retry = test.currentRetry(); + if (retry < test.retries()) { + var clonedTest = test.clone(); + clonedTest.currentRetry(retry + 1); + tests.unshift(clonedTest); - if (err) { - self.fail(test, err); - self.emit('test end', test); - return self.hookUp('afterEach', next); + self.emit(constants.EVENT_TEST_RETRY, test, err); + + // Early return + hook trigger so that it doesn't + // increment the count wrong + return self.hookUp(HOOK_TYPE_AFTER_EACH, next); + } else { + self.fail(test, err); + } + self.emit(constants.EVENT_TEST_END, test); + return self.hookUp(HOOK_TYPE_AFTER_EACH, next); } - test.state = 'passed'; - self.emit('pass', test); - self.emit('test end', test); - self.hookUp('afterEach', next); + test.state = STATE_PASSED; + self.emit(constants.EVENT_TEST_PASS, test); + self.emit(constants.EVENT_TEST_END, test); + self.hookUp(HOOK_TYPE_AFTER_EACH, next); }); }); } this.next = next; + this.hookErr = hookErr; next(); }; +function alwaysFalse() { + return false; +} + /** - * Run the given `suite` and invoke the - * callback `fn()` when complete. + * Run the given `suite` and invoke the callback `fn()` when complete. * + * @private * @param {Suite} suite * @param {Function} fn - * @api private */ - -Runner.prototype.runSuite = function(suite, fn){ - var total = this.grepTotal(suite) - , self = this - , i = 0; +Runner.prototype.runSuite = function(suite, fn) { + var i = 0; + var self = this; + var total = this.grepTotal(suite); debug('run suite %s', suite.fullTitle()); - if (!total) return fn(); - - this.emit('suite', this.suite = suite); - - function next() { - var curr = suite.suites[i++]; - if (!curr) return done(); - self.runSuite(curr, next); + if (!total || (self.failures && suite._bail)) { + return fn(); } - function done() { + this.emit(constants.EVENT_SUITE_BEGIN, (this.suite = suite)); + + function next(errSuite) { + if (errSuite) { + // current suite failed on a hook from errSuite + if (errSuite === suite) { + // if errSuite is current suite + // continue to the next sibling suite + return done(); + } + // errSuite is among the parents of current suite + // stop execution of errSuite and all sub-suites + return done(errSuite); + } + + if (self._abort) { + return done(); + } + + var curr = suite.suites[i++]; + if (!curr) { + return done(); + } + + // Avoid grep neglecting large number of tests causing a + // huge recursive loop and thus a maximum call stack error. + // See comment in `this.runTests()` for more information. + if (self._grep !== self._defaultGrep) { + Runner.immediately(function() { + self.runSuite(curr, next); + }); + } else { + self.runSuite(curr, next); + } + } + + function done(errSuite) { self.suite = suite; - self.hook('afterAll', function(){ - self.emit('suite end', suite); - fn(); + self.nextSuite = next; + + // remove reference to test + delete self.test; + + self.hook(HOOK_TYPE_AFTER_ALL, function() { + self.emit(constants.EVENT_SUITE_END, suite); + fn(errSuite); }); } - this.hook('beforeAll', function(){ + this.nextSuite = next; + + this.hook(HOOK_TYPE_BEFORE_ALL, function(err) { + if (err) { + return done(); + } self.runTests(suite, next); }); }; /** - * Handle uncaught exceptions. + * Handle uncaught exceptions within runner. * * @param {Error} err - * @api private + * @private */ +Runner.prototype.uncaught = function(err) { + if (err instanceof Pending) { + return; + } + // browser does not exit script when throwing in global.onerror() + if (this.allowUncaught && !process.browser) { + throw err; + } -Runner.prototype.uncaught = function(err){ - debug('uncaught exception %s', err.message); - var runnable = this.currentRunnable; - if (!runnable || 'failed' == runnable.state) return; - runnable.clearTimeout(); + if (err) { + debug('uncaught exception %O', err); + } else { + debug('uncaught undefined/falsy exception'); + err = createInvalidExceptionError( + 'Caught falsy/undefined exception which would otherwise be uncaught. No stack trace found; try a debugger', + err + ); + } + + if (!isError(err)) { + err = thrown2Error(err); + } err.uncaught = true; - this.fail(runnable, err); - // recover from test - if ('test' == runnable.type) { - this.emit('test end', runnable); - this.hookUp('afterEach', this.next); + var runnable = this.currentRunnable; + + if (!runnable) { + runnable = new Runnable('Uncaught error outside test suite'); + runnable.parent = this.suite; + + if (this.started) { + this.fail(runnable, err); + } else { + // Can't recover from this failure + this.emit(constants.EVENT_RUN_BEGIN); + this.fail(runnable, err); + this.emit(constants.EVENT_RUN_END); + } + return; } - // bail on hooks - this.emit('end'); + runnable.clearTimeout(); + + if (runnable.isFailed()) { + // Ignore error if already failed + return; + } else if (runnable.isPending()) { + // report 'pending test' retrospectively as failed + runnable.isPending = alwaysFalse; + this.fail(runnable, err); + delete runnable.isPending; + return; + } + + // we cannot recover gracefully if a Runnable has already passed + // then fails asynchronously + if (runnable.isPassed()) { + this.fail(runnable, err); + this.abort(); + } else { + debug(runnable); + return runnable.callback(err); + } +}; + +/** + * Handle uncaught exceptions after runner's end event. + * + * @param {Error} err + * @private + */ +Runner.prototype.uncaughtEnd = function uncaughtEnd(err) { + if (err instanceof Pending) return; + throw err; }; /** * Run the root suite and invoke `fn(failures)` * on completion. * + * @public + * @memberof Runner * @param {Function} fn - * @return {Runner} for chaining - * @api public + * @return {Runner} Runner instance. */ +Runner.prototype.run = function(fn) { + var self = this; + var rootSuite = this.suite; -Runner.prototype.run = function(fn){ - var self = this - , fn = fn || function(){}; + fn = fn || function() {}; - debug('start'); - - // uncaught callback function uncaught(err) { self.uncaught(err); } + function start() { + // If there is an `only` filter + if (rootSuite.hasOnly()) { + rootSuite.filterOnly(); + } + self.started = true; + if (self._delay) { + self.emit(constants.EVENT_DELAY_END); + } + self.emit(constants.EVENT_RUN_BEGIN); + + self.runSuite(rootSuite, function() { + debug('finished running'); + self.emit(constants.EVENT_RUN_END); + }); + } + + debug(constants.EVENT_RUN_BEGIN); + + // references cleanup to avoid memory leaks + this.on(constants.EVENT_SUITE_END, function(suite) { + suite.cleanReferences(); + }); + // callback - this.on('end', function(){ - debug('end'); + this.on(constants.EVENT_RUN_END, function() { + debug(constants.EVENT_RUN_END); process.removeListener('uncaughtException', uncaught); + process.on('uncaughtException', self.uncaughtEnd); fn(self.failures); }); - // run suites - this.emit('start'); - this.runSuite(this.suite, function(){ - debug('finished running'); - self.emit('end'); - }); - // uncaught exception + process.removeListener('uncaughtException', self.uncaughtEnd); process.on('uncaughtException', uncaught); + if (this._delay) { + // for reporters, I guess. + // might be nice to debounce some dots while we wait. + this.emit(constants.EVENT_DELAY_BEGIN, rootSuite); + rootSuite.once(EVENT_ROOT_SUITE_RUN, start); + } else { + Runner.immediately(function() { + start(); + }); + } + + return this; +}; + +/** + * Cleanly abort execution. + * + * @memberof Runner + * @public + * @return {Runner} Runner instance. + */ +Runner.prototype.abort = function() { + debug('aborting'); + this._abort = true; + return this; }; /** * Filter leaks with the given globals flagged as `ok`. * + * @private * @param {Array} ok * @param {Array} globals * @return {Array} - * @api private */ - function filterLeaks(ok, globals) { - return filter(globals, function(key){ - var matched = filter(ok, function(ok){ - if (~ok.indexOf('*')) return 0 == key.indexOf(ok.split('*')[0]); - return key == ok; + return globals.filter(function(key) { + // Firefox and Chrome exposes iframes as index inside the window object + if (/^\d+/.test(key)) { + return false; + } + + // in firefox + // if runner runs in an iframe, this iframe's window.getInterface method + // not init at first it is assigned in some seconds + if (global.navigator && /^getInterface/.test(key)) { + return false; + } + + // an iframe could be approached by window[iframeIndex] + // in ie6,7,8 and opera, iframeIndex is enumerable, this could cause leak + if (global.navigator && /^\d+/.test(key)) { + return false; + } + + // Opera and IE expose global variables for HTML element IDs (issue #243) + if (/^mocha-/.test(key)) { + return false; + } + + var matched = ok.filter(function(ok) { + if (~ok.indexOf('*')) { + return key.indexOf(ok.split('*')[0]) === 0; + } + return key === ok; }); - return matched.length == 0 && (!global.navigator || 'onerror' !== key); + return !matched.length && (!global.navigator || key !== 'onerror'); }); } -}); // module: runner.js +/** + * Check if argument is an instance of Error object or a duck-typed equivalent. + * + * @private + * @param {Object} err - object to check + * @param {string} err.message - error message + * @returns {boolean} + */ +function isError(err) { + return err instanceof Error || (err && typeof err.message === 'string'); +} -require.register("suite.js", function(module, exports, require){ +/** + * + * Converts thrown non-extensible type into proper Error. + * + * @private + * @param {*} thrown - Non-extensible type thrown by code + * @return {Error} + */ +function thrown2Error(err) { + return new Error( + 'the ' + type(err) + ' ' + stringify(err) + ' was thrown, throw an Error :)' + ); +} + +Runner.constants = constants; + +/** + * Node.js' `EventEmitter` + * @external EventEmitter + * @see {@link https://nodejs.org/api/events.html#events_class_eventemitter} + */ + +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./errors":6,"./pending":16,"./runnable":33,"./suite":36,"./utils":38,"_process":69,"debug":45,"events":50,"util":89}],35:[function(require,module,exports){ +(function (global){ +'use strict'; + +/** + * Provides a factory function for a {@link StatsCollector} object. + * @module + */ + +var constants = require('./runner').constants; +var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; +var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; +var EVENT_SUITE_BEGIN = constants.EVENT_SUITE_BEGIN; +var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; +var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING; +var EVENT_RUN_END = constants.EVENT_RUN_END; +var EVENT_TEST_END = constants.EVENT_TEST_END; + +/** + * Test statistics collector. + * + * @public + * @typedef {Object} StatsCollector + * @property {number} suites - integer count of suites run. + * @property {number} tests - integer count of tests run. + * @property {number} passes - integer count of passing tests. + * @property {number} pending - integer count of pending tests. + * @property {number} failures - integer count of failed tests. + * @property {Date} start - time when testing began. + * @property {Date} end - time when testing concluded. + * @property {number} duration - number of msecs that testing took. + */ + +var Date = global.Date; + +/** + * Provides stats such as test duration, number of tests passed / failed etc., by listening for events emitted by `runner`. + * + * @private + * @param {Runner} runner - Runner instance + * @throws {TypeError} If falsy `runner` + */ +function createStatsCollector(runner) { + /** + * @type StatsCollector + */ + var stats = { + suites: 0, + tests: 0, + passes: 0, + pending: 0, + failures: 0 + }; + + if (!runner) { + throw new TypeError('Missing runner argument'); + } + + runner.stats = stats; + + runner.once(EVENT_RUN_BEGIN, function() { + stats.start = new Date(); + }); + runner.on(EVENT_SUITE_BEGIN, function(suite) { + suite.root || stats.suites++; + }); + runner.on(EVENT_TEST_PASS, function() { + stats.passes++; + }); + runner.on(EVENT_TEST_FAIL, function() { + stats.failures++; + }); + runner.on(EVENT_TEST_PENDING, function() { + stats.pending++; + }); + runner.on(EVENT_TEST_END, function() { + stats.tests++; + }); + runner.once(EVENT_RUN_END, function() { + stats.end = new Date(); + stats.duration = stats.end - stats.start; + }); +} + +module.exports = createStatsCollector; + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./runner":34}],36:[function(require,module,exports){ +'use strict'; /** * Module dependencies. */ - -var EventEmitter = require('browser/events').EventEmitter - , debug = require('browser/debug')('mocha:suite') - , milliseconds = require('./ms') - , utils = require('./utils') - , Hook = require('./hook'); +var EventEmitter = require('events').EventEmitter; +var Hook = require('./hook'); +var utils = require('./utils'); +var inherits = utils.inherits; +var debug = require('debug')('mocha:suite'); +var milliseconds = require('ms'); +var errors = require('./errors'); +var createInvalidArgumentTypeError = errors.createInvalidArgumentTypeError; /** * Expose `Suite`. @@ -4149,39 +6714,46 @@ var EventEmitter = require('browser/events').EventEmitter exports = module.exports = Suite; /** - * Create a new `Suite` with the given `title` - * and parent `Suite`. When a suite with the - * same title is already present, that suite - * is returned to provide nicer reporter - * and more flexible meta-testing. + * Create a new `Suite` with the given `title` and parent `Suite`. * - * @param {Suite} parent - * @param {String} title + * @public + * @param {Suite} parent - Parent suite (required!) + * @param {string} title - Title * @return {Suite} - * @api public */ - -exports.create = function(parent, title){ +Suite.create = function(parent, title) { var suite = new Suite(title, parent.ctx); suite.parent = parent; - if (parent.pending) suite.pending = true; title = suite.fullTitle(); parent.addSuite(suite); return suite; }; /** - * Initialize a new `Suite` with the given - * `title` and `ctx`. + * Constructs a new `Suite` instance with the given `title`, `ctx`, and `isRoot`. * - * @param {String} title - * @param {Context} ctx - * @api private + * @public + * @class + * @extends EventEmitter + * @see {@link https://nodejs.org/api/events.html#events_class_eventemitter|EventEmitter} + * @param {string} title - Suite title. + * @param {Context} parentContext - Parent context instance. + * @param {boolean} [isRoot=false] - Whether this is the root suite. */ - -function Suite(title, ctx) { +function Suite(title, parentContext, isRoot) { + if (!utils.isString(title)) { + throw createInvalidArgumentTypeError( + 'Suite argument "title" must be a string. Received type "' + + typeof title + + '"', + 'title', + 'string' + ); + } this.title = title; - this.ctx = ctx; + function Context() {} + Context.prototype = parentContext; + this.ctx = new Context(); this.suites = []; this.tests = []; this.pending = false; @@ -4189,272 +6761,610 @@ function Suite(title, ctx) { this._beforeAll = []; this._afterEach = []; this._afterAll = []; - this.root = !title; + this.root = isRoot === true; this._timeout = 2000; + this._enableTimeouts = true; this._slow = 75; this._bail = false; + this._retries = -1; + this._onlyTests = []; + this._onlySuites = []; + this.delayed = false; + + this.on('newListener', function(event) { + if (deprecatedEvents[event]) { + utils.deprecate( + 'Event "' + + event + + '" is deprecated. Please let the Mocha team know about your use case: https://git.io/v6Lwm' + ); + } + }); } /** * Inherit from `EventEmitter.prototype`. */ - -Suite.prototype = new EventEmitter; -Suite.prototype.constructor = Suite; - +inherits(Suite, EventEmitter); /** * Return a clone of this `Suite`. * + * @private * @return {Suite} - * @api private */ - -Suite.prototype.clone = function(){ +Suite.prototype.clone = function() { var suite = new Suite(this.title); debug('clone'); suite.ctx = this.ctx; + suite.root = this.root; suite.timeout(this.timeout()); + suite.retries(this.retries()); + suite.enableTimeouts(this.enableTimeouts()); suite.slow(this.slow()); suite.bail(this.bail()); return suite; }; /** - * Set timeout `ms` or short-hand such as "2s". + * Set or get timeout `ms` or short-hand such as "2s". * - * @param {Number|String} ms - * @return {Suite|Number} for chaining - * @api private + * @private + * @todo Do not attempt to set value if `ms` is undefined + * @param {number|string} ms + * @return {Suite|number} for chaining */ - -Suite.prototype.timeout = function(ms){ - if (0 == arguments.length) return this._timeout; - if ('string' == typeof ms) ms = milliseconds(ms); +Suite.prototype.timeout = function(ms) { + if (!arguments.length) { + return this._timeout; + } + if (ms.toString() === '0') { + this._enableTimeouts = false; + } + if (typeof ms === 'string') { + ms = milliseconds(ms); + } debug('timeout %d', ms); this._timeout = parseInt(ms, 10); return this; }; /** - * Set slow `ms` or short-hand such as "2s". + * Set or get number of times to retry a failed test. * - * @param {Number|String} ms - * @return {Suite|Number} for chaining - * @api private + * @private + * @param {number|string} n + * @return {Suite|number} for chaining */ +Suite.prototype.retries = function(n) { + if (!arguments.length) { + return this._retries; + } + debug('retries %d', n); + this._retries = parseInt(n, 10) || 0; + return this; +}; -Suite.prototype.slow = function(ms){ - if (0 === arguments.length) return this._slow; - if ('string' == typeof ms) ms = milliseconds(ms); +/** + * Set or get timeout to `enabled`. + * + * @private + * @param {boolean} enabled + * @return {Suite|boolean} self or enabled + */ +Suite.prototype.enableTimeouts = function(enabled) { + if (!arguments.length) { + return this._enableTimeouts; + } + debug('enableTimeouts %s', enabled); + this._enableTimeouts = enabled; + return this; +}; + +/** + * Set or get slow `ms` or short-hand such as "2s". + * + * @private + * @param {number|string} ms + * @return {Suite|number} for chaining + */ +Suite.prototype.slow = function(ms) { + if (!arguments.length) { + return this._slow; + } + if (typeof ms === 'string') { + ms = milliseconds(ms); + } debug('slow %d', ms); this._slow = ms; return this; }; /** - * Sets whether to bail after first error. + * Set or get whether to bail after first error. * - * @parma {Boolean} bail - * @return {Suite|Number} for chaining - * @api private + * @private + * @param {boolean} bail + * @return {Suite|number} for chaining */ - -Suite.prototype.bail = function(bail){ - if (0 == arguments.length) return this._bail; +Suite.prototype.bail = function(bail) { + if (!arguments.length) { + return this._bail; + } debug('bail %s', bail); this._bail = bail; return this; }; /** - * Run `fn(test[, done])` before running tests. + * Check if this suite or its parent suite is marked as pending. * - * @param {Function} fn - * @return {Suite} for chaining - * @api private + * @private */ +Suite.prototype.isPending = function() { + return this.pending || (this.parent && this.parent.isPending()); +}; -Suite.prototype.beforeAll = function(fn){ - if (this.pending) return this; - var hook = new Hook('"before all" hook', fn); +/** + * Generic hook-creator. + * @private + * @param {string} title - Title of hook + * @param {Function} fn - Hook callback + * @returns {Hook} A new hook + */ +Suite.prototype._createHook = function(title, fn) { + var hook = new Hook(title, fn); hook.parent = this; hook.timeout(this.timeout()); + hook.retries(this.retries()); + hook.enableTimeouts(this.enableTimeouts()); hook.slow(this.slow()); hook.ctx = this.ctx; + hook.file = this.file; + return hook; +}; + +/** + * Run `fn(test[, done])` before running tests. + * + * @private + * @param {string} title + * @param {Function} fn + * @return {Suite} for chaining + */ +Suite.prototype.beforeAll = function(title, fn) { + if (this.isPending()) { + return this; + } + if (typeof title === 'function') { + fn = title; + title = fn.name; + } + title = '"before all" hook' + (title ? ': ' + title : ''); + + var hook = this._createHook(title, fn); this._beforeAll.push(hook); - this.emit('beforeAll', hook); + this.emit(constants.EVENT_SUITE_ADD_HOOK_BEFORE_ALL, hook); return this; }; /** * Run `fn(test[, done])` after running tests. * + * @private + * @param {string} title * @param {Function} fn * @return {Suite} for chaining - * @api private */ +Suite.prototype.afterAll = function(title, fn) { + if (this.isPending()) { + return this; + } + if (typeof title === 'function') { + fn = title; + title = fn.name; + } + title = '"after all" hook' + (title ? ': ' + title : ''); -Suite.prototype.afterAll = function(fn){ - if (this.pending) return this; - var hook = new Hook('"after all" hook', fn); - hook.parent = this; - hook.timeout(this.timeout()); - hook.slow(this.slow()); - hook.ctx = this.ctx; + var hook = this._createHook(title, fn); this._afterAll.push(hook); - this.emit('afterAll', hook); + this.emit(constants.EVENT_SUITE_ADD_HOOK_AFTER_ALL, hook); return this; }; /** * Run `fn(test[, done])` before each test case. * + * @private + * @param {string} title * @param {Function} fn * @return {Suite} for chaining - * @api private */ +Suite.prototype.beforeEach = function(title, fn) { + if (this.isPending()) { + return this; + } + if (typeof title === 'function') { + fn = title; + title = fn.name; + } + title = '"before each" hook' + (title ? ': ' + title : ''); -Suite.prototype.beforeEach = function(fn){ - if (this.pending) return this; - var hook = new Hook('"before each" hook', fn); - hook.parent = this; - hook.timeout(this.timeout()); - hook.slow(this.slow()); - hook.ctx = this.ctx; + var hook = this._createHook(title, fn); this._beforeEach.push(hook); - this.emit('beforeEach', hook); + this.emit(constants.EVENT_SUITE_ADD_HOOK_BEFORE_EACH, hook); return this; }; /** * Run `fn(test[, done])` after each test case. * + * @private + * @param {string} title * @param {Function} fn * @return {Suite} for chaining - * @api private */ +Suite.prototype.afterEach = function(title, fn) { + if (this.isPending()) { + return this; + } + if (typeof title === 'function') { + fn = title; + title = fn.name; + } + title = '"after each" hook' + (title ? ': ' + title : ''); -Suite.prototype.afterEach = function(fn){ - if (this.pending) return this; - var hook = new Hook('"after each" hook', fn); - hook.parent = this; - hook.timeout(this.timeout()); - hook.slow(this.slow()); - hook.ctx = this.ctx; + var hook = this._createHook(title, fn); this._afterEach.push(hook); - this.emit('afterEach', hook); + this.emit(constants.EVENT_SUITE_ADD_HOOK_AFTER_EACH, hook); return this; }; /** * Add a test `suite`. * + * @private * @param {Suite} suite * @return {Suite} for chaining - * @api private */ - -Suite.prototype.addSuite = function(suite){ +Suite.prototype.addSuite = function(suite) { suite.parent = this; + suite.root = false; suite.timeout(this.timeout()); + suite.retries(this.retries()); + suite.enableTimeouts(this.enableTimeouts()); suite.slow(this.slow()); suite.bail(this.bail()); this.suites.push(suite); - this.emit('suite', suite); + this.emit(constants.EVENT_SUITE_ADD_SUITE, suite); return this; }; /** * Add a `test` to this suite. * + * @private * @param {Test} test * @return {Suite} for chaining - * @api private */ - -Suite.prototype.addTest = function(test){ +Suite.prototype.addTest = function(test) { test.parent = this; test.timeout(this.timeout()); + test.retries(this.retries()); + test.enableTimeouts(this.enableTimeouts()); test.slow(this.slow()); test.ctx = this.ctx; this.tests.push(test); - this.emit('test', test); + this.emit(constants.EVENT_SUITE_ADD_TEST, test); return this; }; /** - * Return the full title generated by recursively - * concatenating the parent's full title. + * Return the full title generated by recursively concatenating the parent's + * full title. * - * @return {String} - * @api public + * @memberof Suite + * @public + * @return {string} */ +Suite.prototype.fullTitle = function() { + return this.titlePath().join(' '); +}; -Suite.prototype.fullTitle = function(){ +/** + * Return the title path generated by recursively concatenating the parent's + * title path. + * + * @memberof Suite + * @public + * @return {string} + */ +Suite.prototype.titlePath = function() { + var result = []; if (this.parent) { - var full = this.parent.fullTitle(); - if (full) return full + ' ' + this.title; + result = result.concat(this.parent.titlePath()); } - return this.title; + if (!this.root) { + result.push(this.title); + } + return result; }; /** * Return the total number of tests. * - * @return {Number} - * @api public + * @memberof Suite + * @public + * @return {number} */ - -Suite.prototype.total = function(){ - return utils.reduce(this.suites, function(sum, suite){ - return sum + suite.total(); - }, 0) + this.tests.length; +Suite.prototype.total = function() { + return ( + this.suites.reduce(function(sum, suite) { + return sum + suite.total(); + }, 0) + this.tests.length + ); }; /** - * Iterates through each suite recursively to find - * all tests. Applies a function in the format - * `fn(test)`. + * Iterates through each suite recursively to find all tests. Applies a + * function in the format `fn(test)`. * + * @private * @param {Function} fn * @return {Suite} - * @api private */ - -Suite.prototype.eachTest = function(fn){ - utils.forEach(this.tests, fn); - utils.forEach(this.suites, function(suite){ +Suite.prototype.eachTest = function(fn) { + this.tests.forEach(fn); + this.suites.forEach(function(suite) { suite.eachTest(fn); }); return this; }; -}); // module: suite.js - -require.register("test.js", function(module, exports, require){ +/** + * This will run the root suite if we happen to be running in delayed mode. + * @private + */ +Suite.prototype.run = function run() { + if (this.root) { + this.emit(constants.EVENT_ROOT_SUITE_RUN); + } +}; /** - * Module dependencies. + * Determines whether a suite has an `only` test or suite as a descendant. + * + * @private + * @returns {Boolean} */ +Suite.prototype.hasOnly = function hasOnly() { + return ( + this._onlyTests.length > 0 || + this._onlySuites.length > 0 || + this.suites.some(function(suite) { + return suite.hasOnly(); + }) + ); +}; +/** + * Filter suites based on `isOnly` logic. + * + * @private + * @returns {Boolean} + */ +Suite.prototype.filterOnly = function filterOnly() { + if (this._onlyTests.length) { + // If the suite contains `only` tests, run those and ignore any nested suites. + this.tests = this._onlyTests; + this.suites = []; + } else { + // Otherwise, do not run any of the tests in this suite. + this.tests = []; + this._onlySuites.forEach(function(onlySuite) { + // If there are other `only` tests/suites nested in the current `only` suite, then filter that `only` suite. + // Otherwise, all of the tests on this `only` suite should be run, so don't filter it. + if (onlySuite.hasOnly()) { + onlySuite.filterOnly(); + } + }); + // Run the `only` suites, as well as any other suites that have `only` tests/suites as descendants. + var onlySuites = this._onlySuites; + this.suites = this.suites.filter(function(childSuite) { + return onlySuites.indexOf(childSuite) !== -1 || childSuite.filterOnly(); + }); + } + // Keep the suite only if there is something to run + return this.tests.length > 0 || this.suites.length > 0; +}; + +/** + * Adds a suite to the list of subsuites marked `only`. + * + * @private + * @param {Suite} suite + */ +Suite.prototype.appendOnlySuite = function(suite) { + this._onlySuites.push(suite); +}; + +/** + * Adds a test to the list of tests marked `only`. + * + * @private + * @param {Test} test + */ +Suite.prototype.appendOnlyTest = function(test) { + this._onlyTests.push(test); +}; + +/** + * Returns the array of hooks by hook name; see `HOOK_TYPE_*` constants. + * @private + */ +Suite.prototype.getHooks = function getHooks(name) { + return this['_' + name]; +}; + +/** + * Cleans up the references to all the deferred functions + * (before/after/beforeEach/afterEach) and tests of a Suite. + * These must be deleted otherwise a memory leak can happen, + * as those functions may reference variables from closures, + * thus those variables can never be garbage collected as long + * as the deferred functions exist. + * + * @private + */ +Suite.prototype.cleanReferences = function cleanReferences() { + function cleanArrReferences(arr) { + for (var i = 0; i < arr.length; i++) { + delete arr[i].fn; + } + } + + if (Array.isArray(this._beforeAll)) { + cleanArrReferences(this._beforeAll); + } + + if (Array.isArray(this._beforeEach)) { + cleanArrReferences(this._beforeEach); + } + + if (Array.isArray(this._afterAll)) { + cleanArrReferences(this._afterAll); + } + + if (Array.isArray(this._afterEach)) { + cleanArrReferences(this._afterEach); + } + + for (var i = 0; i < this.tests.length; i++) { + delete this.tests[i].fn; + } +}; + +var constants = utils.defineConstants( + /** + * {@link Suite}-related constants. + * @public + * @memberof Suite + * @alias constants + * @readonly + * @static + * @enum {string} + */ + { + /** + * Event emitted after a test file has been loaded Not emitted in browser. + */ + EVENT_FILE_POST_REQUIRE: 'post-require', + /** + * Event emitted before a test file has been loaded. In browser, this is emitted once an interface has been selected. + */ + EVENT_FILE_PRE_REQUIRE: 'pre-require', + /** + * Event emitted immediately after a test file has been loaded. Not emitted in browser. + */ + EVENT_FILE_REQUIRE: 'require', + /** + * Event emitted when `global.run()` is called (use with `delay` option) + */ + EVENT_ROOT_SUITE_RUN: 'run', + + /** + * Namespace for collection of a `Suite`'s "after all" hooks + */ + HOOK_TYPE_AFTER_ALL: 'afterAll', + /** + * Namespace for collection of a `Suite`'s "after each" hooks + */ + HOOK_TYPE_AFTER_EACH: 'afterEach', + /** + * Namespace for collection of a `Suite`'s "before all" hooks + */ + HOOK_TYPE_BEFORE_ALL: 'beforeAll', + /** + * Namespace for collection of a `Suite`'s "before all" hooks + */ + HOOK_TYPE_BEFORE_EACH: 'beforeEach', + + // the following events are all deprecated + + /** + * Emitted after an "after all" `Hook` has been added to a `Suite`. Deprecated + */ + EVENT_SUITE_ADD_HOOK_AFTER_ALL: 'afterAll', + /** + * Emitted after an "after each" `Hook` has been added to a `Suite` Deprecated + */ + EVENT_SUITE_ADD_HOOK_AFTER_EACH: 'afterEach', + /** + * Emitted after an "before all" `Hook` has been added to a `Suite` Deprecated + */ + EVENT_SUITE_ADD_HOOK_BEFORE_ALL: 'beforeAll', + /** + * Emitted after an "before each" `Hook` has been added to a `Suite` Deprecated + */ + EVENT_SUITE_ADD_HOOK_BEFORE_EACH: 'beforeEach', + /** + * Emitted after a child `Suite` has been added to a `Suite`. Deprecated + */ + EVENT_SUITE_ADD_SUITE: 'suite', + /** + * Emitted after a `Test` has been added to a `Suite`. Deprecated + */ + EVENT_SUITE_ADD_TEST: 'test' + } +); + +/** + * @summary There are no known use cases for these events. + * @desc This is a `Set`-like object having all keys being the constant's string value and the value being `true`. + * @todo Remove eventually + * @type {Object} + * @ignore + */ +var deprecatedEvents = Object.keys(constants) + .filter(function(constant) { + return constant.substring(0, 15) === 'EVENT_SUITE_ADD'; + }) + .reduce(function(acc, constant) { + acc[constants[constant]] = true; + return acc; + }, utils.createMap()); + +Suite.constants = constants; + +},{"./errors":6,"./hook":7,"./utils":38,"debug":45,"events":50,"ms":60}],37:[function(require,module,exports){ +'use strict'; var Runnable = require('./runnable'); - -/** - * Expose `Test`. - */ +var utils = require('./utils'); +var errors = require('./errors'); +var createInvalidArgumentTypeError = errors.createInvalidArgumentTypeError; +var isString = utils.isString; module.exports = Test; /** * Initialize a new `Test` with the given `title` and callback `fn`. * - * @param {String} title - * @param {Function} fn - * @api private + * @public + * @class + * @extends Runnable + * @param {String} title - Test title (required) + * @param {Function} [fn] - Test callback. If omitted, the Test is considered "pending" */ - function Test(title, fn) { + if (!isString(title)) { + throw createInvalidArgumentTypeError( + 'Test argument "title" should be a string. Received type "' + + typeof title + + '"', + 'title', + 'string' + ); + } Runnable.call(this, title, fn); this.pending = !fn; this.type = 'test'; @@ -4463,196 +7373,100 @@ function Test(title, fn) { /** * Inherit from `Runnable.prototype`. */ +utils.inherits(Test, Runnable); -Test.prototype = new Runnable; -Test.prototype.constructor = Test; +/** + * Set or get retried test + * + * @private + */ +Test.prototype.retriedTest = function(n) { + if (!arguments.length) { + return this._retriedTest; + } + this._retriedTest = n; +}; +Test.prototype.clone = function() { + var test = new Test(this.title, this.fn); + test.timeout(this.timeout()); + test.slow(this.slow()); + test.enableTimeouts(this.enableTimeouts()); + test.retries(this.retries()); + test.currentRetry(this.currentRetry()); + test.retriedTest(this.retriedTest() || this); + test.globals(this.globals()); + test.parent = this.parent; + test.file = this.file; + test.ctx = this.ctx; + return test; +}; -}); // module: test.js +},{"./errors":6,"./runnable":33,"./utils":38}],38:[function(require,module,exports){ +(function (process,Buffer){ +'use strict'; -require.register("utils.js", function(module, exports, require){ +/** + * Various utility functions used throughout Mocha's codebase. + * @module utils + */ /** * Module dependencies. */ -var fs = require('browser/fs') - , path = require('browser/path') - , join = path.join - , debug = require('browser/debug')('mocha:watch'); +var fs = require('fs'); +var path = require('path'); +var util = require('util'); +var glob = require('glob'); +var he = require('he'); +var errors = require('./errors'); +var createNoFilesMatchPatternError = errors.createNoFilesMatchPatternError; +var createMissingArgumentError = errors.createMissingArgumentError; + +var assign = (exports.assign = require('object.assign').getPolyfill()); /** - * Ignored directories. + * Inherit the prototype methods from one constructor into another. + * + * @param {function} ctor - Constructor function which needs to inherit the + * prototype. + * @param {function} superCtor - Constructor function to inherit prototype from. + * @throws {TypeError} if either constructor is null, or if super constructor + * lacks a prototype. */ - -var ignore = ['node_modules', '.git']; +exports.inherits = util.inherits; /** * Escape special characters in the given string of html. * - * @param {String} html - * @return {String} - * @api private + * @private + * @param {string} html + * @return {string} */ - -exports.escape = function(html){ - return String(html) - .replace(/&/g, '&') - .replace(/"/g, '"') - .replace(//g, '>'); +exports.escape = function(html) { + return he.encode(String(html), {useNamedReferences: false}); }; /** - * Array#forEach (<=IE8) - * - * @param {Array} array - * @param {Function} fn - * @param {Object} scope - * @api private - */ - -exports.forEach = function(arr, fn, scope){ - for (var i = 0, l = arr.length; i < l; i++) - fn.call(scope, arr[i], i); -}; - -/** - * Array#indexOf (<=IE8) - * - * @parma {Array} arr - * @param {Object} obj to find index of - * @param {Number} start - * @api private - */ - -exports.indexOf = function(arr, obj, start){ - for (var i = start || 0, l = arr.length; i < l; i++) { - if (arr[i] === obj) - return i; - } - return -1; -}; - -/** - * Array#reduce (<=IE8) - * - * @param {Array} array - * @param {Function} fn - * @param {Object} initial value - * @api private - */ - -exports.reduce = function(arr, fn, val){ - var rval = val; - - for (var i = 0, l = arr.length; i < l; i++) { - rval = fn(rval, arr[i], i, arr); - } - - return rval; -}; - -/** - * Array#filter (<=IE8) - * - * @param {Array} array - * @param {Function} fn - * @api private - */ - -exports.filter = function(arr, fn){ - var ret = []; - - for (var i = 0, l = arr.length; i < l; i++) { - var val = arr[i]; - if (fn(val, i, arr)) ret.push(val); - } - - return ret; -}; - -/** - * Object.keys (<=IE8) + * Test if the given obj is type of string. * + * @private * @param {Object} obj - * @return {Array} keys - * @api private + * @return {boolean} */ - -exports.keys = Object.keys || function(obj) { - var keys = [] - , has = Object.prototype.hasOwnProperty // for `window` on <=IE8 - - for (var key in obj) { - if (has.call(obj, key)) { - keys.push(key); - } - } - - return keys; -}; - -/** - * Watch the given `files` for changes - * and invoke `fn(file)` on modification. - * - * @param {Array} files - * @param {Function} fn - * @api private - */ - -exports.watch = function(files, fn){ - var options = { interval: 100 }; - files.forEach(function(file){ - debug('file %s', file); - fs.watchFile(file, options, function(curr, prev){ - if (prev.mtime < curr.mtime) fn(file); - }); - }); -}; - -/** - * Ignored files. - */ - -function ignored(path){ - return !~ignore.indexOf(path); -} - -/** - * Lookup files in the given `dir`. - * - * @return {Array} - * @api private - */ - -exports.files = function(dir, ret){ - ret = ret || []; - - fs.readdirSync(dir) - .filter(ignored) - .forEach(function(path){ - path = join(dir, path); - if (fs.statSync(path).isDirectory()) { - exports.files(path, ret); - } else if (path.match(/\.(js|coffee)$/)) { - ret.push(path); - } - }); - - return ret; +exports.isString = function(obj) { + return typeof obj === 'string'; }; /** * Compute a slug from the given `str`. * - * @param {String} str - * @return {String} - * @api private + * @private + * @param {string} str + * @return {string} */ - -exports.slug = function(str){ +exports.slug = function(str) { return str .toLowerCase() .replace(/ +/g, '-') @@ -4660,74 +7474,63 @@ exports.slug = function(str){ }; /** - * Strip the function definition from `str`, - * and re-indent for pre whitespace. + * Strip the function definition from `str`, and re-indent for pre whitespace. + * + * @param {string} str + * @return {string} */ - exports.clean = function(str) { str = str - .replace(/^function *\(.*\) *{/, '') - .replace(/\s+\}$/, ''); + .replace(/\r\n?|[\n\u2028\u2029]/g, '\n') + .replace(/^\uFEFF/, '') + // (traditional)-> space/name parameters body (lambda)-> parameters body multi-statement/single keep body content + .replace( + /^function(?:\s*|\s+[^(]*)\([^)]*\)\s*\{((?:.|\n)*?)\s*\}$|^\([^)]*\)\s*=>\s*(?:\{((?:.|\n)*?)\s*\}|((?:.|\n)*))$/, + '$1$2$3' + ); - var spaces = str.match(/^\n?( *)/)[1].length - , re = new RegExp('^ {' + spaces + '}', 'gm'); + var spaces = str.match(/^\n?( *)/)[1].length; + var tabs = str.match(/^\n?(\t*)/)[1].length; + var re = new RegExp( + '^\n?' + (tabs ? '\t' : ' ') + '{' + (tabs || spaces) + '}', + 'gm' + ); str = str.replace(re, ''); - return exports.trim(str); -}; - -/** - * Escape regular expression characters in `str`. - * - * @param {String} str - * @return {String} - * @api private - */ - -exports.escapeRegexp = function(str){ - return str.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&"); -}; - -/** - * Trim the given `str`. - * - * @param {String} str - * @return {String} - * @api private - */ - -exports.trim = function(str){ - return str.replace(/^\s+|\s+$/g, ''); + return str.trim(); }; /** * Parse the given `qs`. * - * @param {String} qs + * @private + * @param {string} qs * @return {Object} - * @api private */ +exports.parseQuery = function(qs) { + return qs + .replace('?', '') + .split('&') + .reduce(function(obj, pair) { + var i = pair.indexOf('='); + var key = pair.slice(0, i); + var val = pair.slice(++i); -exports.parseQuery = function(qs){ - return exports.reduce(qs.replace('?', '').split('&'), function(obj, pair){ - var i = pair.indexOf('=') - , key = pair.slice(0, i) - , val = pair.slice(++i); + // Due to how the URLSearchParams API treats spaces + obj[key] = decodeURIComponent(val.replace(/\+/g, '%20')); - obj[key] = decodeURIComponent(val); - return obj; - }, {}); + return obj; + }, {}); }; /** * Highlight the given string of `js`. * - * @param {String} js - * @return {String} - * @api private + * @private + * @param {string} js + * @return {string} */ - function highlight(js) { return js .replace(/$1') .replace(/(\d+\.\d+)/gm, '$1') .replace(/(\d+)/gm, '$1') - .replace(/\bnew *(\w+)/gm, 'new $1') - .replace(/\b(function|new|throw|return|var|if|else)\b/gm, '$1') + .replace( + /\bnew[ \t]+(\w+)/gm, + 'new $1' + ) + .replace( + /\b(function|new|throw|return|var|if|else)\b/gm, + '$1' + ); } /** * Highlight the contents of tag `name`. * - * @param {String} name - * @api private + * @private + * @param {string} name */ - exports.highlightTags = function(name) { - var code = document.getElementsByTagName(name); + var code = document.getElementById('mocha').getElementsByTagName(name); for (var i = 0, len = code.length; i < len; ++i) { code[i].innerHTML = highlight(code[i].innerHTML); } }; -}); // module: utils.js /** - * Node shims. + * If a value could have properties, and has none, this function is called, + * which returns a string representation of the empty value. * - * These are meant only to allow - * mocha.js to run untouched, not - * to allow running node code in - * the browser. + * Functions w/ no properties return `'[Function]'` + * Arrays w/ length === 0 return `'[]'` + * Objects w/ no properties return `'{}'` + * All else: return result of `value.toString()` + * + * @private + * @param {*} value The value to inspect. + * @param {string} typeHint The type of the value + * @returns {string} */ - -process = {}; -process.exit = function(status){}; -process.stdout = {}; -global = window; +function emptyRepresentation(value, typeHint) { + switch (typeHint) { + case 'function': + return '[Function]'; + case 'object': + return '{}'; + case 'array': + return '[]'; + default: + return value.toString(); + } +} /** - * next tick implementation. + * Takes some variable and asks `Object.prototype.toString()` what it thinks it + * is. + * + * @private + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString + * @param {*} value The value to test. + * @returns {string} Computed type + * @example + * type({}) // 'object' + * type([]) // 'array' + * type(1) // 'number' + * type(false) // 'boolean' + * type(Infinity) // 'number' + * type(null) // 'null' + * type(new Date()) // 'date' + * type(/foo/) // 'regexp' + * type('type') // 'string' + * type(global) // 'global' + * type(new String('foo') // 'object' */ - -process.nextTick = (function(){ - // postMessage behaves badly on IE8 - if (window.ActiveXObject || !window.postMessage) { - return function(fn){ fn() }; +var type = (exports.type = function type(value) { + if (value === undefined) { + return 'undefined'; + } else if (value === null) { + return 'null'; + } else if (Buffer.isBuffer(value)) { + return 'buffer'; } + return Object.prototype.toString + .call(value) + .replace(/^\[.+\s(.+?)]$/, '$1') + .toLowerCase(); +}); - // based on setZeroTimeout by David Baron - // - http://dbaron.org/log/20100309-faster-timeouts - var timeouts = [] - , name = 'mocha-zero-timeout' +/** + * Stringify `value`. Different behavior depending on type of value: + * + * - If `value` is undefined or null, return `'[undefined]'` or `'[null]'`, respectively. + * - If `value` is not an object, function or array, return result of `value.toString()` wrapped in double-quotes. + * - If `value` is an *empty* object, function, or array, return result of function + * {@link emptyRepresentation}. + * - If `value` has properties, call {@link exports.canonicalize} on it, then return result of + * JSON.stringify(). + * + * @private + * @see exports.type + * @param {*} value + * @return {string} + */ +exports.stringify = function(value) { + var typeHint = type(value); - window.addEventListener('message', function(e){ - if (e.source == window && e.data == name) { - if (e.stopPropagation) e.stopPropagation(); - if (timeouts.length) timeouts.shift()(); + if (!~['object', 'array', 'function'].indexOf(typeHint)) { + if (typeHint === 'buffer') { + var json = Buffer.prototype.toJSON.call(value); + // Based on the toJSON result + return jsonStringify( + json.data && json.type ? json.data : json, + 2 + ).replace(/,(\n|$)/g, '$1'); } - }, true); - return function(fn){ - timeouts.push(fn); - window.postMessage(name, '*'); + // IE7/IE8 has a bizarre String constructor; needs to be coerced + // into an array and back to obj. + if (typeHint === 'string' && typeof value === 'object') { + value = value.split('').reduce(function(acc, char, idx) { + acc[idx] = char; + return acc; + }, {}); + typeHint = 'object'; + } else { + return jsonStringify(value); + } } -})(); + + for (var prop in value) { + if (Object.prototype.hasOwnProperty.call(value, prop)) { + return jsonStringify( + exports.canonicalize(value, null, typeHint), + 2 + ).replace(/,(\n|$)/g, '$1'); + } + } + + return emptyRepresentation(value, typeHint); +}; /** - * Remove uncaughtException listener. + * like JSON.stringify but more sense. + * + * @private + * @param {Object} object + * @param {number=} spaces + * @param {number=} depth + * @returns {*} */ +function jsonStringify(object, spaces, depth) { + if (typeof spaces === 'undefined') { + // primitive types + return _stringify(object); + } -process.removeListener = function(e){ - if ('uncaughtException' == e) { - window.onerror = null; + depth = depth || 1; + var space = spaces * depth; + var str = Array.isArray(object) ? '[' : '{'; + var end = Array.isArray(object) ? ']' : '}'; + var length = + typeof object.length === 'number' + ? object.length + : Object.keys(object).length; + // `.repeat()` polyfill + function repeat(s, n) { + return new Array(n).join(s); + } + + function _stringify(val) { + switch (type(val)) { + case 'null': + case 'undefined': + val = '[' + val + ']'; + break; + case 'array': + case 'object': + val = jsonStringify(val, spaces, depth + 1); + break; + case 'boolean': + case 'regexp': + case 'symbol': + case 'number': + val = + val === 0 && 1 / val === -Infinity // `-0` + ? '-0' + : val.toString(); + break; + case 'date': + var sDate = isNaN(val.getTime()) ? val.toString() : val.toISOString(); + val = '[Date: ' + sDate + ']'; + break; + case 'buffer': + var json = val.toJSON(); + // Based on the toJSON result + json = json.data && json.type ? json.data : json; + val = '[Buffer: ' + jsonStringify(json, 2, depth + 1) + ']'; + break; + default: + val = + val === '[Function]' || val === '[Circular]' + ? val + : JSON.stringify(val); // string + } + return val; + } + + for (var i in object) { + if (!Object.prototype.hasOwnProperty.call(object, i)) { + continue; // not my business + } + --length; + str += + '\n ' + + repeat(' ', space) + + (Array.isArray(object) ? '' : '"' + i + '": ') + // key + _stringify(object[i]) + // value + (length ? ',' : ''); // comma + } + + return ( + str + + // [], {} + (str.length !== 1 ? '\n' + repeat(' ', --space) + end : end) + ); +} + +/** + * Return a new Thing that has the keys in sorted order. Recursive. + * + * If the Thing... + * - has already been seen, return string `'[Circular]'` + * - is `undefined`, return string `'[undefined]'` + * - is `null`, return value `null` + * - is some other primitive, return the value + * - is not a primitive or an `Array`, `Object`, or `Function`, return the value of the Thing's `toString()` method + * - is a non-empty `Array`, `Object`, or `Function`, return the result of calling this function again. + * - is an empty `Array`, `Object`, or `Function`, return the result of calling `emptyRepresentation()` + * + * @private + * @see {@link exports.stringify} + * @param {*} value Thing to inspect. May or may not have properties. + * @param {Array} [stack=[]] Stack of seen values + * @param {string} [typeHint] Type hint + * @return {(Object|Array|Function|string|undefined)} + */ +exports.canonicalize = function canonicalize(value, stack, typeHint) { + var canonicalizedObj; + /* eslint-disable no-unused-vars */ + var prop; + /* eslint-enable no-unused-vars */ + typeHint = typeHint || type(value); + function withStack(value, fn) { + stack.push(value); + fn(); + stack.pop(); + } + + stack = stack || []; + + if (stack.indexOf(value) !== -1) { + return '[Circular]'; + } + + switch (typeHint) { + case 'undefined': + case 'buffer': + case 'null': + canonicalizedObj = value; + break; + case 'array': + withStack(value, function() { + canonicalizedObj = value.map(function(item) { + return exports.canonicalize(item, stack); + }); + }); + break; + case 'function': + /* eslint-disable guard-for-in */ + for (prop in value) { + canonicalizedObj = {}; + break; + } + /* eslint-enable guard-for-in */ + if (!canonicalizedObj) { + canonicalizedObj = emptyRepresentation(value, typeHint); + break; + } + /* falls through */ + case 'object': + canonicalizedObj = canonicalizedObj || {}; + withStack(value, function() { + Object.keys(value) + .sort() + .forEach(function(key) { + canonicalizedObj[key] = exports.canonicalize(value[key], stack); + }); + }); + break; + case 'date': + case 'number': + case 'regexp': + case 'boolean': + case 'symbol': + canonicalizedObj = value; + break; + default: + canonicalizedObj = value + ''; + } + + return canonicalizedObj; +}; + +/** + * Determines if pathname has a matching file extension. + * + * @private + * @param {string} pathname - Pathname to check for match. + * @param {string[]} exts - List of file extensions (sans period). + * @return {boolean} whether file extension matches. + * @example + * hasMatchingExtname('foo.html', ['js', 'css']); // => false + */ +function hasMatchingExtname(pathname, exts) { + var suffix = path.extname(pathname).slice(1); + return exts.some(function(element) { + return suffix === element; + }); +} + +/** + * Determines if pathname would be a "hidden" file (or directory) on UN*X. + * + * @description + * On UN*X, pathnames beginning with a full stop (aka dot) are hidden during + * typical usage. Dotfiles, plain-text configuration files, are prime examples. + * + * @see {@link http://xahlee.info/UnixResource_dir/writ/unix_origin_of_dot_filename.html|Origin of Dot File Names} + * + * @private + * @param {string} pathname - Pathname to check for match. + * @return {boolean} whether pathname would be considered a hidden file. + * @example + * isHiddenOnUnix('.profile'); // => true + */ +function isHiddenOnUnix(pathname) { + return path.basename(pathname)[0] === '.'; +} + +/** + * Lookup file names at the given `path`. + * + * @description + * Filenames are returned in _traversal_ order by the OS/filesystem. + * **Make no assumption that the names will be sorted in any fashion.** + * + * @public + * @memberof Mocha.utils + * @param {string} filepath - Base path to start searching from. + * @param {string[]} [extensions=[]] - File extensions to look for. + * @param {boolean} [recursive=false] - Whether to recurse into subdirectories. + * @return {string[]} An array of paths. + * @throws {Error} if no files match pattern. + * @throws {TypeError} if `filepath` is directory and `extensions` not provided. + */ +exports.lookupFiles = function lookupFiles(filepath, extensions, recursive) { + extensions = extensions || []; + recursive = recursive || false; + var files = []; + var stat; + + if (!fs.existsSync(filepath)) { + var pattern; + if (glob.hasMagic(filepath)) { + // Handle glob as is without extensions + pattern = filepath; + } else { + // glob pattern e.g. 'filepath+(.js|.ts)' + var strExtensions = extensions + .map(function(v) { + return '.' + v; + }) + .join('|'); + pattern = filepath + '+(' + strExtensions + ')'; + } + files = glob.sync(pattern, {nodir: true}); + if (!files.length) { + throw createNoFilesMatchPatternError( + 'Cannot find any files matching pattern ' + exports.dQuote(filepath), + filepath + ); + } + return files; + } + + // Handle file + try { + stat = fs.statSync(filepath); + if (stat.isFile()) { + return filepath; + } + } catch (err) { + // ignore error + return; + } + + // Handle directory + fs.readdirSync(filepath).forEach(function(dirent) { + var pathname = path.join(filepath, dirent); + var stat; + + try { + stat = fs.statSync(pathname); + if (stat.isDirectory()) { + if (recursive) { + files = files.concat(lookupFiles(pathname, extensions, recursive)); + } + return; + } + } catch (err) { + // ignore error + return; + } + if (!extensions.length) { + throw createMissingArgumentError( + util.format( + 'Argument %s required when argument %s is a directory', + exports.sQuote('extensions'), + exports.sQuote('filepath') + ), + 'extensions', + 'array' + ); + } + + if ( + !stat.isFile() || + !hasMatchingExtname(pathname, extensions) || + isHiddenOnUnix(pathname) + ) { + return; + } + files.push(pathname); + }); + + return files; +}; + +/** + * process.emitWarning or a polyfill + * @see https://nodejs.org/api/process.html#process_process_emitwarning_warning_options + * @ignore + */ +function emitWarning(msg, type) { + if (process.emitWarning) { + process.emitWarning(msg, type); + } else { + process.nextTick(function() { + console.warn(type + ': ' + msg); + }); + } +} + +/** + * Show a deprecation warning. Each distinct message is only displayed once. + * Ignores empty messages. + * + * @param {string} [msg] - Warning to print + * @private + */ +exports.deprecate = function deprecate(msg) { + msg = String(msg); + if (msg && !deprecate.cache[msg]) { + deprecate.cache[msg] = true; + emitWarning(msg, 'DeprecationWarning'); + } +}; +exports.deprecate.cache = {}; + +/** + * Show a generic warning. + * Ignores empty messages. + * + * @param {string} [msg] - Warning to print + * @private + */ +exports.warn = function warn(msg) { + if (msg) { + emitWarning(msg); } }; /** - * Implements uncaughtException listener. + * @summary + * This Filter based on `mocha-clean` module.(see: `github.com/rstacruz/mocha-clean`) + * @description + * When invoking this function you get a filter function that get the Error.stack as an input, + * and return a prettify output. + * (i.e: strip Mocha and internal node functions from stack trace). + * @returns {Function} */ +exports.stackTraceFilter = function() { + // TODO: Replace with `process.browser` + var is = typeof document === 'undefined' ? {node: true} : {browser: true}; + var slash = path.sep; + var cwd; + if (is.node) { + cwd = process.cwd() + slash; + } else { + cwd = (typeof location === 'undefined' + ? window.location + : location + ).href.replace(/\/[^/]*$/, '/'); + slash = '/'; + } -process.on = function(e, fn){ - if ('uncaughtException' == e) { - window.onerror = fn; + function isMochaInternal(line) { + return ( + ~line.indexOf('node_modules' + slash + 'mocha' + slash) || + ~line.indexOf(slash + 'mocha.js') || + ~line.indexOf(slash + 'mocha.min.js') + ); + } + + function isNodeInternal(line) { + return ( + ~line.indexOf('(timers.js:') || + ~line.indexOf('(events.js:') || + ~line.indexOf('(node.js:') || + ~line.indexOf('(module.js:') || + ~line.indexOf('GeneratorFunctionPrototype.next (native)') || + false + ); + } + + return function(stack) { + stack = stack.split('\n'); + + stack = stack.reduce(function(list, line) { + if (isMochaInternal(line)) { + return list; + } + + if (is.node && isNodeInternal(line)) { + return list; + } + + // Clean up cwd(absolute) + if (/:\d+:\d+\)?$/.test(line)) { + line = line.replace('(' + cwd, '('); + } + + list.push(line); + return list; + }, []); + + return stack.join('\n'); + }; +}; + +/** + * Crude, but effective. + * @public + * @param {*} value + * @returns {boolean} Whether or not `value` is a Promise + */ +exports.isPromise = function isPromise(value) { + return ( + typeof value === 'object' && + value !== null && + typeof value.then === 'function' + ); +}; + +/** + * Clamps a numeric value to an inclusive range. + * + * @param {number} value - Value to be clamped. + * @param {numer[]} range - Two element array specifying [min, max] range. + * @returns {number} clamped value + */ +exports.clamp = function clamp(value, range) { + return Math.min(Math.max(value, range[0]), range[1]); +}; + +/** + * Single quote text by combining with undirectional ASCII quotation marks. + * + * @description + * Provides a simple means of markup for quoting text to be used in output. + * Use this to quote names of variables, methods, and packages. + * + * package 'foo' cannot be found + * + * @private + * @param {string} str - Value to be quoted. + * @returns {string} quoted value + * @example + * sQuote('n') // => 'n' + */ +exports.sQuote = function(str) { + return "'" + str + "'"; +}; + +/** + * Double quote text by combining with undirectional ASCII quotation marks. + * + * @description + * Provides a simple means of markup for quoting text to be used in output. + * Use this to quote names of datatypes, classes, pathnames, and strings. + * + * argument 'value' must be "string" or "number" + * + * @private + * @param {string} str - Value to be quoted. + * @returns {string} quoted value + * @example + * dQuote('number') // => "number" + */ +exports.dQuote = function(str) { + return '"' + str + '"'; +}; + +/** + * It's a noop. + * @public + */ +exports.noop = function() {}; + +/** + * Creates a map-like object. + * + * @description + * A "map" is an object with no prototype, for our purposes. In some cases + * this would be more appropriate than a `Map`, especially if your environment + * doesn't support it. Recommended for use in Mocha's public APIs. + * + * @public + * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map|MDN:Map} + * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create#Custom_and_Null_objects|MDN:Object.create - Custom objects} + * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign|MDN:Object.assign} + * @param {...*} [obj] - Arguments to `Object.assign()`. + * @returns {Object} An object with no prototype, having `...obj` properties + */ +exports.createMap = function(obj) { + return assign.apply( + null, + [Object.create(null)].concat(Array.prototype.slice.call(arguments)) + ); +}; + +/** + * Creates a read-only map-like object. + * + * @description + * This differs from {@link module:utils.createMap createMap} only in that + * the argument must be non-empty, because the result is frozen. + * + * @see {@link module:utils.createMap createMap} + * @param {...*} [obj] - Arguments to `Object.assign()`. + * @returns {Object} A frozen object with no prototype, having `...obj` properties + * @throws {TypeError} if argument is not a non-empty object. + */ +exports.defineConstants = function(obj) { + if (type(obj) !== 'object' || !Object.keys(obj).length) { + throw new TypeError('Invalid argument; expected a non-empty object'); + } + return Object.freeze(exports.createMap(obj)); +}; + +/** + * Whether current version of Node support ES modules + * + * @description + * Versions prior to 10 did not support ES Modules, and version 10 has an old incompatibile version of ESM. + * This function returns whether Node.JS has ES Module supports that is compatible with Mocha's needs, + * which is version >=12.11. + * + * @returns {Boolean} whether the current version of Node.JS supports ES Modules in a way that is compatible with Mocha + */ +exports.supportsEsModules = function() { + if (!process.browser && process.versions && process.versions.node) { + var versionFields = process.versions.node.split('.'); + var major = +versionFields[0]; + var minor = +versionFields[1]; + + if (major >= 13 || (major === 12 && minor >= 11)) { + return true; + } } }; -// boot -;(function(){ +}).call(this,require('_process'),require("buffer").Buffer) +},{"./errors":6,"_process":69,"buffer":43,"fs":42,"glob":42,"he":54,"object.assign":65,"path":42,"util":89}],39:[function(require,module,exports){ +'use strict' +exports.byteLength = byteLength +exports.toByteArray = toByteArray +exports.fromByteArray = fromByteArray + +var lookup = [] +var revLookup = [] +var Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array + +var code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' +for (var i = 0, len = code.length; i < len; ++i) { + lookup[i] = code[i] + revLookup[code.charCodeAt(i)] = i +} + +// Support decoding URL-safe base64 strings, as Node.js does. +// See: https://en.wikipedia.org/wiki/Base64#URL_applications +revLookup['-'.charCodeAt(0)] = 62 +revLookup['_'.charCodeAt(0)] = 63 + +function getLens (b64) { + var len = b64.length + + if (len % 4 > 0) { + throw new Error('Invalid string. Length must be a multiple of 4') + } + + // Trim off extra bytes after placeholder bytes are found + // See: https://github.com/beatgammit/base64-js/issues/42 + var validLen = b64.indexOf('=') + if (validLen === -1) validLen = len + + var placeHoldersLen = validLen === len + ? 0 + : 4 - (validLen % 4) + + return [validLen, placeHoldersLen] +} + +// base64 is 4/3 + up to two characters of the original data +function byteLength (b64) { + var lens = getLens(b64) + var validLen = lens[0] + var placeHoldersLen = lens[1] + return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen +} + +function _byteLength (b64, validLen, placeHoldersLen) { + return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen +} + +function toByteArray (b64) { + var tmp + var lens = getLens(b64) + var validLen = lens[0] + var placeHoldersLen = lens[1] + + var arr = new Arr(_byteLength(b64, validLen, placeHoldersLen)) + + var curByte = 0 + + // if there are placeholders, only get up to the last complete 4 chars + var len = placeHoldersLen > 0 + ? validLen - 4 + : validLen + + for (var i = 0; i < len; i += 4) { + tmp = + (revLookup[b64.charCodeAt(i)] << 18) | + (revLookup[b64.charCodeAt(i + 1)] << 12) | + (revLookup[b64.charCodeAt(i + 2)] << 6) | + revLookup[b64.charCodeAt(i + 3)] + arr[curByte++] = (tmp >> 16) & 0xFF + arr[curByte++] = (tmp >> 8) & 0xFF + arr[curByte++] = tmp & 0xFF + } + + if (placeHoldersLen === 2) { + tmp = + (revLookup[b64.charCodeAt(i)] << 2) | + (revLookup[b64.charCodeAt(i + 1)] >> 4) + arr[curByte++] = tmp & 0xFF + } + + if (placeHoldersLen === 1) { + tmp = + (revLookup[b64.charCodeAt(i)] << 10) | + (revLookup[b64.charCodeAt(i + 1)] << 4) | + (revLookup[b64.charCodeAt(i + 2)] >> 2) + arr[curByte++] = (tmp >> 8) & 0xFF + arr[curByte++] = tmp & 0xFF + } + + return arr +} + +function tripletToBase64 (num) { + return lookup[num >> 18 & 0x3F] + + lookup[num >> 12 & 0x3F] + + lookup[num >> 6 & 0x3F] + + lookup[num & 0x3F] +} + +function encodeChunk (uint8, start, end) { + var tmp + var output = [] + for (var i = start; i < end; i += 3) { + tmp = + ((uint8[i] << 16) & 0xFF0000) + + ((uint8[i + 1] << 8) & 0xFF00) + + (uint8[i + 2] & 0xFF) + output.push(tripletToBase64(tmp)) + } + return output.join('') +} + +function fromByteArray (uint8) { + var tmp + var len = uint8.length + var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes + var parts = [] + var maxChunkLength = 16383 // must be multiple of 3 + + // go through the array every three bytes, we'll deal with trailing stuff later + for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) { + parts.push(encodeChunk( + uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength) + )) + } + + // pad the end with zeros, but make sure to not forget the extra bytes + if (extraBytes === 1) { + tmp = uint8[len - 1] + parts.push( + lookup[tmp >> 2] + + lookup[(tmp << 4) & 0x3F] + + '==' + ) + } else if (extraBytes === 2) { + tmp = (uint8[len - 2] << 8) + uint8[len - 1] + parts.push( + lookup[tmp >> 10] + + lookup[(tmp >> 4) & 0x3F] + + lookup[(tmp << 2) & 0x3F] + + '=' + ) + } + + return parts.join('') +} + +},{}],40:[function(require,module,exports){ + +},{}],41:[function(require,module,exports){ +(function (process){ +var WritableStream = require('stream').Writable +var inherits = require('util').inherits + +module.exports = BrowserStdout + + +inherits(BrowserStdout, WritableStream) + +function BrowserStdout(opts) { + if (!(this instanceof BrowserStdout)) return new BrowserStdout(opts) + + opts = opts || {} + WritableStream.call(this, opts) + this.label = (opts.label !== undefined) ? opts.label : 'stdout' +} + +BrowserStdout.prototype._write = function(chunks, encoding, cb) { + var output = chunks.toString ? chunks.toString() : chunks + if (this.label === false) { + console.log(output) + } else { + console.log(this.label+':', output) + } + process.nextTick(cb) +} + +}).call(this,require('_process')) +},{"_process":69,"stream":84,"util":89}],42:[function(require,module,exports){ +arguments[4][40][0].apply(exports,arguments) +},{"dup":40}],43:[function(require,module,exports){ +(function (Buffer){ +/*! + * The buffer module from node.js, for the browser. + * + * @author Feross Aboukhadijeh + * @license MIT + */ +/* eslint-disable no-proto */ + +'use strict' + +var base64 = require('base64-js') +var ieee754 = require('ieee754') + +exports.Buffer = Buffer +exports.SlowBuffer = SlowBuffer +exports.INSPECT_MAX_BYTES = 50 + +var K_MAX_LENGTH = 0x7fffffff +exports.kMaxLength = K_MAX_LENGTH + +/** + * If `Buffer.TYPED_ARRAY_SUPPORT`: + * === true Use Uint8Array implementation (fastest) + * === false Print warning and recommend using `buffer` v4.x which has an Object + * implementation (most compatible, even IE6) + * + * Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+, + * Opera 11.6+, iOS 4.2+. + * + * We report that the browser does not support typed arrays if the are not subclassable + * using __proto__. Firefox 4-29 lacks support for adding new properties to `Uint8Array` + * (See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438). IE 10 lacks support + * for __proto__ and has a buggy typed array implementation. + */ +Buffer.TYPED_ARRAY_SUPPORT = typedArraySupport() + +if (!Buffer.TYPED_ARRAY_SUPPORT && typeof console !== 'undefined' && + typeof console.error === 'function') { + console.error( + 'This browser lacks typed array (Uint8Array) support which is required by ' + + '`buffer` v5.x. Use `buffer` v4.x if you require old browser support.' + ) +} + +function typedArraySupport () { + // Can typed array instances can be augmented? + try { + var arr = new Uint8Array(1) + arr.__proto__ = { __proto__: Uint8Array.prototype, foo: function () { return 42 } } + return arr.foo() === 42 + } catch (e) { + return false + } +} + +Object.defineProperty(Buffer.prototype, 'parent', { + enumerable: true, + get: function () { + if (!Buffer.isBuffer(this)) return undefined + return this.buffer + } +}) + +Object.defineProperty(Buffer.prototype, 'offset', { + enumerable: true, + get: function () { + if (!Buffer.isBuffer(this)) return undefined + return this.byteOffset + } +}) + +function createBuffer (length) { + if (length > K_MAX_LENGTH) { + throw new RangeError('The value "' + length + '" is invalid for option "size"') + } + // Return an augmented `Uint8Array` instance + var buf = new Uint8Array(length) + buf.__proto__ = Buffer.prototype + return buf +} + +/** + * The Buffer constructor returns instances of `Uint8Array` that have their + * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of + * `Uint8Array`, so the returned instances will have all the node `Buffer` methods + * and the `Uint8Array` methods. Square bracket notation works as expected -- it + * returns a single octet. + * + * The `Uint8Array` prototype remains unmodified. + */ + +function Buffer (arg, encodingOrOffset, length) { + // Common case. + if (typeof arg === 'number') { + if (typeof encodingOrOffset === 'string') { + throw new TypeError( + 'The "string" argument must be of type string. Received type number' + ) + } + return allocUnsafe(arg) + } + return from(arg, encodingOrOffset, length) +} + +// Fix subarray() in ES2016. See: https://github.com/feross/buffer/pull/97 +if (typeof Symbol !== 'undefined' && Symbol.species != null && + Buffer[Symbol.species] === Buffer) { + Object.defineProperty(Buffer, Symbol.species, { + value: null, + configurable: true, + enumerable: false, + writable: false + }) +} + +Buffer.poolSize = 8192 // not used by this implementation + +function from (value, encodingOrOffset, length) { + if (typeof value === 'string') { + return fromString(value, encodingOrOffset) + } + + if (ArrayBuffer.isView(value)) { + return fromArrayLike(value) + } + + if (value == null) { + throw TypeError( + 'The first argument must be one of type string, Buffer, ArrayBuffer, Array, ' + + 'or Array-like Object. Received type ' + (typeof value) + ) + } + + if (isInstance(value, ArrayBuffer) || + (value && isInstance(value.buffer, ArrayBuffer))) { + return fromArrayBuffer(value, encodingOrOffset, length) + } + + if (typeof value === 'number') { + throw new TypeError( + 'The "value" argument must not be of type number. Received type number' + ) + } + + var valueOf = value.valueOf && value.valueOf() + if (valueOf != null && valueOf !== value) { + return Buffer.from(valueOf, encodingOrOffset, length) + } + + var b = fromObject(value) + if (b) return b + + if (typeof Symbol !== 'undefined' && Symbol.toPrimitive != null && + typeof value[Symbol.toPrimitive] === 'function') { + return Buffer.from( + value[Symbol.toPrimitive]('string'), encodingOrOffset, length + ) + } + + throw new TypeError( + 'The first argument must be one of type string, Buffer, ArrayBuffer, Array, ' + + 'or Array-like Object. Received type ' + (typeof value) + ) +} + +/** + * Functionally equivalent to Buffer(arg, encoding) but throws a TypeError + * if value is a number. + * Buffer.from(str[, encoding]) + * Buffer.from(array) + * Buffer.from(buffer) + * Buffer.from(arrayBuffer[, byteOffset[, length]]) + **/ +Buffer.from = function (value, encodingOrOffset, length) { + return from(value, encodingOrOffset, length) +} + +// Note: Change prototype *after* Buffer.from is defined to workaround Chrome bug: +// https://github.com/feross/buffer/pull/148 +Buffer.prototype.__proto__ = Uint8Array.prototype +Buffer.__proto__ = Uint8Array + +function assertSize (size) { + if (typeof size !== 'number') { + throw new TypeError('"size" argument must be of type number') + } else if (size < 0) { + throw new RangeError('The value "' + size + '" is invalid for option "size"') + } +} + +function alloc (size, fill, encoding) { + assertSize(size) + if (size <= 0) { + return createBuffer(size) + } + if (fill !== undefined) { + // Only pay attention to encoding if it's a string. This + // prevents accidentally sending in a number that would + // be interpretted as a start offset. + return typeof encoding === 'string' + ? createBuffer(size).fill(fill, encoding) + : createBuffer(size).fill(fill) + } + return createBuffer(size) +} + +/** + * Creates a new filled Buffer instance. + * alloc(size[, fill[, encoding]]) + **/ +Buffer.alloc = function (size, fill, encoding) { + return alloc(size, fill, encoding) +} + +function allocUnsafe (size) { + assertSize(size) + return createBuffer(size < 0 ? 0 : checked(size) | 0) +} + +/** + * Equivalent to Buffer(num), by default creates a non-zero-filled Buffer instance. + * */ +Buffer.allocUnsafe = function (size) { + return allocUnsafe(size) +} +/** + * Equivalent to SlowBuffer(num), by default creates a non-zero-filled Buffer instance. + */ +Buffer.allocUnsafeSlow = function (size) { + return allocUnsafe(size) +} + +function fromString (string, encoding) { + if (typeof encoding !== 'string' || encoding === '') { + encoding = 'utf8' + } + + if (!Buffer.isEncoding(encoding)) { + throw new TypeError('Unknown encoding: ' + encoding) + } + + var length = byteLength(string, encoding) | 0 + var buf = createBuffer(length) + + var actual = buf.write(string, encoding) + + if (actual !== length) { + // Writing a hex string, for example, that contains invalid characters will + // cause everything after the first invalid character to be ignored. (e.g. + // 'abxxcd' will be treated as 'ab') + buf = buf.slice(0, actual) + } + + return buf +} + +function fromArrayLike (array) { + var length = array.length < 0 ? 0 : checked(array.length) | 0 + var buf = createBuffer(length) + for (var i = 0; i < length; i += 1) { + buf[i] = array[i] & 255 + } + return buf +} + +function fromArrayBuffer (array, byteOffset, length) { + if (byteOffset < 0 || array.byteLength < byteOffset) { + throw new RangeError('"offset" is outside of buffer bounds') + } + + if (array.byteLength < byteOffset + (length || 0)) { + throw new RangeError('"length" is outside of buffer bounds') + } + + var buf + if (byteOffset === undefined && length === undefined) { + buf = new Uint8Array(array) + } else if (length === undefined) { + buf = new Uint8Array(array, byteOffset) + } else { + buf = new Uint8Array(array, byteOffset, length) + } + + // Return an augmented `Uint8Array` instance + buf.__proto__ = Buffer.prototype + return buf +} + +function fromObject (obj) { + if (Buffer.isBuffer(obj)) { + var len = checked(obj.length) | 0 + var buf = createBuffer(len) + + if (buf.length === 0) { + return buf + } + + obj.copy(buf, 0, 0, len) + return buf + } + + if (obj.length !== undefined) { + if (typeof obj.length !== 'number' || numberIsNaN(obj.length)) { + return createBuffer(0) + } + return fromArrayLike(obj) + } + + if (obj.type === 'Buffer' && Array.isArray(obj.data)) { + return fromArrayLike(obj.data) + } +} + +function checked (length) { + // Note: cannot use `length < K_MAX_LENGTH` here because that fails when + // length is NaN (which is otherwise coerced to zero.) + if (length >= K_MAX_LENGTH) { + throw new RangeError('Attempt to allocate Buffer larger than maximum ' + + 'size: 0x' + K_MAX_LENGTH.toString(16) + ' bytes') + } + return length | 0 +} + +function SlowBuffer (length) { + if (+length != length) { // eslint-disable-line eqeqeq + length = 0 + } + return Buffer.alloc(+length) +} + +Buffer.isBuffer = function isBuffer (b) { + return b != null && b._isBuffer === true && + b !== Buffer.prototype // so Buffer.isBuffer(Buffer.prototype) will be false +} + +Buffer.compare = function compare (a, b) { + if (isInstance(a, Uint8Array)) a = Buffer.from(a, a.offset, a.byteLength) + if (isInstance(b, Uint8Array)) b = Buffer.from(b, b.offset, b.byteLength) + if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) { + throw new TypeError( + 'The "buf1", "buf2" arguments must be one of type Buffer or Uint8Array' + ) + } + + if (a === b) return 0 + + var x = a.length + var y = b.length + + for (var i = 0, len = Math.min(x, y); i < len; ++i) { + if (a[i] !== b[i]) { + x = a[i] + y = b[i] + break + } + } + + if (x < y) return -1 + if (y < x) return 1 + return 0 +} + +Buffer.isEncoding = function isEncoding (encoding) { + switch (String(encoding).toLowerCase()) { + case 'hex': + case 'utf8': + case 'utf-8': + case 'ascii': + case 'latin1': + case 'binary': + case 'base64': + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return true + default: + return false + } +} + +Buffer.concat = function concat (list, length) { + if (!Array.isArray(list)) { + throw new TypeError('"list" argument must be an Array of Buffers') + } + + if (list.length === 0) { + return Buffer.alloc(0) + } + + var i + if (length === undefined) { + length = 0 + for (i = 0; i < list.length; ++i) { + length += list[i].length + } + } + + var buffer = Buffer.allocUnsafe(length) + var pos = 0 + for (i = 0; i < list.length; ++i) { + var buf = list[i] + if (isInstance(buf, Uint8Array)) { + buf = Buffer.from(buf) + } + if (!Buffer.isBuffer(buf)) { + throw new TypeError('"list" argument must be an Array of Buffers') + } + buf.copy(buffer, pos) + pos += buf.length + } + return buffer +} + +function byteLength (string, encoding) { + if (Buffer.isBuffer(string)) { + return string.length + } + if (ArrayBuffer.isView(string) || isInstance(string, ArrayBuffer)) { + return string.byteLength + } + if (typeof string !== 'string') { + throw new TypeError( + 'The "string" argument must be one of type string, Buffer, or ArrayBuffer. ' + + 'Received type ' + typeof string + ) + } + + var len = string.length + var mustMatch = (arguments.length > 2 && arguments[2] === true) + if (!mustMatch && len === 0) return 0 + + // Use a for loop to avoid recursion + var loweredCase = false + for (;;) { + switch (encoding) { + case 'ascii': + case 'latin1': + case 'binary': + return len + case 'utf8': + case 'utf-8': + return utf8ToBytes(string).length + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return len * 2 + case 'hex': + return len >>> 1 + case 'base64': + return base64ToBytes(string).length + default: + if (loweredCase) { + return mustMatch ? -1 : utf8ToBytes(string).length // assume utf8 + } + encoding = ('' + encoding).toLowerCase() + loweredCase = true + } + } +} +Buffer.byteLength = byteLength + +function slowToString (encoding, start, end) { + var loweredCase = false + + // No need to verify that "this.length <= MAX_UINT32" since it's a read-only + // property of a typed array. + + // This behaves neither like String nor Uint8Array in that we set start/end + // to their upper/lower bounds if the value passed is out of range. + // undefined is handled specially as per ECMA-262 6th Edition, + // Section 13.3.3.7 Runtime Semantics: KeyedBindingInitialization. + if (start === undefined || start < 0) { + start = 0 + } + // Return early if start > this.length. Done here to prevent potential uint32 + // coercion fail below. + if (start > this.length) { + return '' + } + + if (end === undefined || end > this.length) { + end = this.length + } + + if (end <= 0) { + return '' + } + + // Force coersion to uint32. This will also coerce falsey/NaN values to 0. + end >>>= 0 + start >>>= 0 + + if (end <= start) { + return '' + } + + if (!encoding) encoding = 'utf8' + + while (true) { + switch (encoding) { + case 'hex': + return hexSlice(this, start, end) + + case 'utf8': + case 'utf-8': + return utf8Slice(this, start, end) + + case 'ascii': + return asciiSlice(this, start, end) + + case 'latin1': + case 'binary': + return latin1Slice(this, start, end) + + case 'base64': + return base64Slice(this, start, end) + + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return utf16leSlice(this, start, end) + + default: + if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding) + encoding = (encoding + '').toLowerCase() + loweredCase = true + } + } +} + +// This property is used by `Buffer.isBuffer` (and the `is-buffer` npm package) +// to detect a Buffer instance. It's not possible to use `instanceof Buffer` +// reliably in a browserify context because there could be multiple different +// copies of the 'buffer' package in use. This method works even for Buffer +// instances that were created from another copy of the `buffer` package. +// See: https://github.com/feross/buffer/issues/154 +Buffer.prototype._isBuffer = true + +function swap (b, n, m) { + var i = b[n] + b[n] = b[m] + b[m] = i +} + +Buffer.prototype.swap16 = function swap16 () { + var len = this.length + if (len % 2 !== 0) { + throw new RangeError('Buffer size must be a multiple of 16-bits') + } + for (var i = 0; i < len; i += 2) { + swap(this, i, i + 1) + } + return this +} + +Buffer.prototype.swap32 = function swap32 () { + var len = this.length + if (len % 4 !== 0) { + throw new RangeError('Buffer size must be a multiple of 32-bits') + } + for (var i = 0; i < len; i += 4) { + swap(this, i, i + 3) + swap(this, i + 1, i + 2) + } + return this +} + +Buffer.prototype.swap64 = function swap64 () { + var len = this.length + if (len % 8 !== 0) { + throw new RangeError('Buffer size must be a multiple of 64-bits') + } + for (var i = 0; i < len; i += 8) { + swap(this, i, i + 7) + swap(this, i + 1, i + 6) + swap(this, i + 2, i + 5) + swap(this, i + 3, i + 4) + } + return this +} + +Buffer.prototype.toString = function toString () { + var length = this.length + if (length === 0) return '' + if (arguments.length === 0) return utf8Slice(this, 0, length) + return slowToString.apply(this, arguments) +} + +Buffer.prototype.toLocaleString = Buffer.prototype.toString + +Buffer.prototype.equals = function equals (b) { + if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer') + if (this === b) return true + return Buffer.compare(this, b) === 0 +} + +Buffer.prototype.inspect = function inspect () { + var str = '' + var max = exports.INSPECT_MAX_BYTES + str = this.toString('hex', 0, max).replace(/(.{2})/g, '$1 ').trim() + if (this.length > max) str += ' ... ' + return '' +} + +Buffer.prototype.compare = function compare (target, start, end, thisStart, thisEnd) { + if (isInstance(target, Uint8Array)) { + target = Buffer.from(target, target.offset, target.byteLength) + } + if (!Buffer.isBuffer(target)) { + throw new TypeError( + 'The "target" argument must be one of type Buffer or Uint8Array. ' + + 'Received type ' + (typeof target) + ) + } + + if (start === undefined) { + start = 0 + } + if (end === undefined) { + end = target ? target.length : 0 + } + if (thisStart === undefined) { + thisStart = 0 + } + if (thisEnd === undefined) { + thisEnd = this.length + } + + if (start < 0 || end > target.length || thisStart < 0 || thisEnd > this.length) { + throw new RangeError('out of range index') + } + + if (thisStart >= thisEnd && start >= end) { + return 0 + } + if (thisStart >= thisEnd) { + return -1 + } + if (start >= end) { + return 1 + } + + start >>>= 0 + end >>>= 0 + thisStart >>>= 0 + thisEnd >>>= 0 + + if (this === target) return 0 + + var x = thisEnd - thisStart + var y = end - start + var len = Math.min(x, y) + + var thisCopy = this.slice(thisStart, thisEnd) + var targetCopy = target.slice(start, end) + + for (var i = 0; i < len; ++i) { + if (thisCopy[i] !== targetCopy[i]) { + x = thisCopy[i] + y = targetCopy[i] + break + } + } + + if (x < y) return -1 + if (y < x) return 1 + return 0 +} + +// Finds either the first index of `val` in `buffer` at offset >= `byteOffset`, +// OR the last index of `val` in `buffer` at offset <= `byteOffset`. +// +// Arguments: +// - buffer - a Buffer to search +// - val - a string, Buffer, or number +// - byteOffset - an index into `buffer`; will be clamped to an int32 +// - encoding - an optional encoding, relevant is val is a string +// - dir - true for indexOf, false for lastIndexOf +function bidirectionalIndexOf (buffer, val, byteOffset, encoding, dir) { + // Empty buffer means no match + if (buffer.length === 0) return -1 + + // Normalize byteOffset + if (typeof byteOffset === 'string') { + encoding = byteOffset + byteOffset = 0 + } else if (byteOffset > 0x7fffffff) { + byteOffset = 0x7fffffff + } else if (byteOffset < -0x80000000) { + byteOffset = -0x80000000 + } + byteOffset = +byteOffset // Coerce to Number. + if (numberIsNaN(byteOffset)) { + // byteOffset: it it's undefined, null, NaN, "foo", etc, search whole buffer + byteOffset = dir ? 0 : (buffer.length - 1) + } + + // Normalize byteOffset: negative offsets start from the end of the buffer + if (byteOffset < 0) byteOffset = buffer.length + byteOffset + if (byteOffset >= buffer.length) { + if (dir) return -1 + else byteOffset = buffer.length - 1 + } else if (byteOffset < 0) { + if (dir) byteOffset = 0 + else return -1 + } + + // Normalize val + if (typeof val === 'string') { + val = Buffer.from(val, encoding) + } + + // Finally, search either indexOf (if dir is true) or lastIndexOf + if (Buffer.isBuffer(val)) { + // Special case: looking for empty string/buffer always fails + if (val.length === 0) { + return -1 + } + return arrayIndexOf(buffer, val, byteOffset, encoding, dir) + } else if (typeof val === 'number') { + val = val & 0xFF // Search for a byte value [0-255] + if (typeof Uint8Array.prototype.indexOf === 'function') { + if (dir) { + return Uint8Array.prototype.indexOf.call(buffer, val, byteOffset) + } else { + return Uint8Array.prototype.lastIndexOf.call(buffer, val, byteOffset) + } + } + return arrayIndexOf(buffer, [ val ], byteOffset, encoding, dir) + } + + throw new TypeError('val must be string, number or Buffer') +} + +function arrayIndexOf (arr, val, byteOffset, encoding, dir) { + var indexSize = 1 + var arrLength = arr.length + var valLength = val.length + + if (encoding !== undefined) { + encoding = String(encoding).toLowerCase() + if (encoding === 'ucs2' || encoding === 'ucs-2' || + encoding === 'utf16le' || encoding === 'utf-16le') { + if (arr.length < 2 || val.length < 2) { + return -1 + } + indexSize = 2 + arrLength /= 2 + valLength /= 2 + byteOffset /= 2 + } + } + + function read (buf, i) { + if (indexSize === 1) { + return buf[i] + } else { + return buf.readUInt16BE(i * indexSize) + } + } + + var i + if (dir) { + var foundIndex = -1 + for (i = byteOffset; i < arrLength; i++) { + if (read(arr, i) === read(val, foundIndex === -1 ? 0 : i - foundIndex)) { + if (foundIndex === -1) foundIndex = i + if (i - foundIndex + 1 === valLength) return foundIndex * indexSize + } else { + if (foundIndex !== -1) i -= i - foundIndex + foundIndex = -1 + } + } + } else { + if (byteOffset + valLength > arrLength) byteOffset = arrLength - valLength + for (i = byteOffset; i >= 0; i--) { + var found = true + for (var j = 0; j < valLength; j++) { + if (read(arr, i + j) !== read(val, j)) { + found = false + break + } + } + if (found) return i + } + } + + return -1 +} + +Buffer.prototype.includes = function includes (val, byteOffset, encoding) { + return this.indexOf(val, byteOffset, encoding) !== -1 +} + +Buffer.prototype.indexOf = function indexOf (val, byteOffset, encoding) { + return bidirectionalIndexOf(this, val, byteOffset, encoding, true) +} + +Buffer.prototype.lastIndexOf = function lastIndexOf (val, byteOffset, encoding) { + return bidirectionalIndexOf(this, val, byteOffset, encoding, false) +} + +function hexWrite (buf, string, offset, length) { + offset = Number(offset) || 0 + var remaining = buf.length - offset + if (!length) { + length = remaining + } else { + length = Number(length) + if (length > remaining) { + length = remaining + } + } + + var strLen = string.length + + if (length > strLen / 2) { + length = strLen / 2 + } + for (var i = 0; i < length; ++i) { + var parsed = parseInt(string.substr(i * 2, 2), 16) + if (numberIsNaN(parsed)) return i + buf[offset + i] = parsed + } + return i +} + +function utf8Write (buf, string, offset, length) { + return blitBuffer(utf8ToBytes(string, buf.length - offset), buf, offset, length) +} + +function asciiWrite (buf, string, offset, length) { + return blitBuffer(asciiToBytes(string), buf, offset, length) +} + +function latin1Write (buf, string, offset, length) { + return asciiWrite(buf, string, offset, length) +} + +function base64Write (buf, string, offset, length) { + return blitBuffer(base64ToBytes(string), buf, offset, length) +} + +function ucs2Write (buf, string, offset, length) { + return blitBuffer(utf16leToBytes(string, buf.length - offset), buf, offset, length) +} + +Buffer.prototype.write = function write (string, offset, length, encoding) { + // Buffer#write(string) + if (offset === undefined) { + encoding = 'utf8' + length = this.length + offset = 0 + // Buffer#write(string, encoding) + } else if (length === undefined && typeof offset === 'string') { + encoding = offset + length = this.length + offset = 0 + // Buffer#write(string, offset[, length][, encoding]) + } else if (isFinite(offset)) { + offset = offset >>> 0 + if (isFinite(length)) { + length = length >>> 0 + if (encoding === undefined) encoding = 'utf8' + } else { + encoding = length + length = undefined + } + } else { + throw new Error( + 'Buffer.write(string, encoding, offset[, length]) is no longer supported' + ) + } + + var remaining = this.length - offset + if (length === undefined || length > remaining) length = remaining + + if ((string.length > 0 && (length < 0 || offset < 0)) || offset > this.length) { + throw new RangeError('Attempt to write outside buffer bounds') + } + + if (!encoding) encoding = 'utf8' + + var loweredCase = false + for (;;) { + switch (encoding) { + case 'hex': + return hexWrite(this, string, offset, length) + + case 'utf8': + case 'utf-8': + return utf8Write(this, string, offset, length) + + case 'ascii': + return asciiWrite(this, string, offset, length) + + case 'latin1': + case 'binary': + return latin1Write(this, string, offset, length) + + case 'base64': + // Warning: maxLength not taken into account in base64Write + return base64Write(this, string, offset, length) + + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return ucs2Write(this, string, offset, length) + + default: + if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding) + encoding = ('' + encoding).toLowerCase() + loweredCase = true + } + } +} + +Buffer.prototype.toJSON = function toJSON () { + return { + type: 'Buffer', + data: Array.prototype.slice.call(this._arr || this, 0) + } +} + +function base64Slice (buf, start, end) { + if (start === 0 && end === buf.length) { + return base64.fromByteArray(buf) + } else { + return base64.fromByteArray(buf.slice(start, end)) + } +} + +function utf8Slice (buf, start, end) { + end = Math.min(buf.length, end) + var res = [] + + var i = start + while (i < end) { + var firstByte = buf[i] + var codePoint = null + var bytesPerSequence = (firstByte > 0xEF) ? 4 + : (firstByte > 0xDF) ? 3 + : (firstByte > 0xBF) ? 2 + : 1 + + if (i + bytesPerSequence <= end) { + var secondByte, thirdByte, fourthByte, tempCodePoint + + switch (bytesPerSequence) { + case 1: + if (firstByte < 0x80) { + codePoint = firstByte + } + break + case 2: + secondByte = buf[i + 1] + if ((secondByte & 0xC0) === 0x80) { + tempCodePoint = (firstByte & 0x1F) << 0x6 | (secondByte & 0x3F) + if (tempCodePoint > 0x7F) { + codePoint = tempCodePoint + } + } + break + case 3: + secondByte = buf[i + 1] + thirdByte = buf[i + 2] + if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80) { + tempCodePoint = (firstByte & 0xF) << 0xC | (secondByte & 0x3F) << 0x6 | (thirdByte & 0x3F) + if (tempCodePoint > 0x7FF && (tempCodePoint < 0xD800 || tempCodePoint > 0xDFFF)) { + codePoint = tempCodePoint + } + } + break + case 4: + secondByte = buf[i + 1] + thirdByte = buf[i + 2] + fourthByte = buf[i + 3] + if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80 && (fourthByte & 0xC0) === 0x80) { + tempCodePoint = (firstByte & 0xF) << 0x12 | (secondByte & 0x3F) << 0xC | (thirdByte & 0x3F) << 0x6 | (fourthByte & 0x3F) + if (tempCodePoint > 0xFFFF && tempCodePoint < 0x110000) { + codePoint = tempCodePoint + } + } + } + } + + if (codePoint === null) { + // we did not generate a valid codePoint so insert a + // replacement char (U+FFFD) and advance only 1 byte + codePoint = 0xFFFD + bytesPerSequence = 1 + } else if (codePoint > 0xFFFF) { + // encode to utf16 (surrogate pair dance) + codePoint -= 0x10000 + res.push(codePoint >>> 10 & 0x3FF | 0xD800) + codePoint = 0xDC00 | codePoint & 0x3FF + } + + res.push(codePoint) + i += bytesPerSequence + } + + return decodeCodePointsArray(res) +} + +// Based on http://stackoverflow.com/a/22747272/680742, the browser with +// the lowest limit is Chrome, with 0x10000 args. +// We go 1 magnitude less, for safety +var MAX_ARGUMENTS_LENGTH = 0x1000 + +function decodeCodePointsArray (codePoints) { + var len = codePoints.length + if (len <= MAX_ARGUMENTS_LENGTH) { + return String.fromCharCode.apply(String, codePoints) // avoid extra slice() + } + + // Decode in chunks to avoid "call stack size exceeded". + var res = '' + var i = 0 + while (i < len) { + res += String.fromCharCode.apply( + String, + codePoints.slice(i, i += MAX_ARGUMENTS_LENGTH) + ) + } + return res +} + +function asciiSlice (buf, start, end) { + var ret = '' + end = Math.min(buf.length, end) + + for (var i = start; i < end; ++i) { + ret += String.fromCharCode(buf[i] & 0x7F) + } + return ret +} + +function latin1Slice (buf, start, end) { + var ret = '' + end = Math.min(buf.length, end) + + for (var i = start; i < end; ++i) { + ret += String.fromCharCode(buf[i]) + } + return ret +} + +function hexSlice (buf, start, end) { + var len = buf.length + + if (!start || start < 0) start = 0 + if (!end || end < 0 || end > len) end = len + + var out = '' + for (var i = start; i < end; ++i) { + out += toHex(buf[i]) + } + return out +} + +function utf16leSlice (buf, start, end) { + var bytes = buf.slice(start, end) + var res = '' + for (var i = 0; i < bytes.length; i += 2) { + res += String.fromCharCode(bytes[i] + (bytes[i + 1] * 256)) + } + return res +} + +Buffer.prototype.slice = function slice (start, end) { + var len = this.length + start = ~~start + end = end === undefined ? len : ~~end + + if (start < 0) { + start += len + if (start < 0) start = 0 + } else if (start > len) { + start = len + } + + if (end < 0) { + end += len + if (end < 0) end = 0 + } else if (end > len) { + end = len + } + + if (end < start) end = start + + var newBuf = this.subarray(start, end) + // Return an augmented `Uint8Array` instance + newBuf.__proto__ = Buffer.prototype + return newBuf +} + +/* + * Need to make sure that buffer isn't trying to write out of bounds. + */ +function checkOffset (offset, ext, length) { + if ((offset % 1) !== 0 || offset < 0) throw new RangeError('offset is not uint') + if (offset + ext > length) throw new RangeError('Trying to access beyond buffer length') +} + +Buffer.prototype.readUIntLE = function readUIntLE (offset, byteLength, noAssert) { + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) + + var val = this[offset] + var mul = 1 + var i = 0 + while (++i < byteLength && (mul *= 0x100)) { + val += this[offset + i] * mul + } + + return val +} + +Buffer.prototype.readUIntBE = function readUIntBE (offset, byteLength, noAssert) { + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) { + checkOffset(offset, byteLength, this.length) + } + + var val = this[offset + --byteLength] + var mul = 1 + while (byteLength > 0 && (mul *= 0x100)) { + val += this[offset + --byteLength] * mul + } + + return val +} + +Buffer.prototype.readUInt8 = function readUInt8 (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 1, this.length) + return this[offset] +} + +Buffer.prototype.readUInt16LE = function readUInt16LE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 2, this.length) + return this[offset] | (this[offset + 1] << 8) +} + +Buffer.prototype.readUInt16BE = function readUInt16BE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 2, this.length) + return (this[offset] << 8) | this[offset + 1] +} + +Buffer.prototype.readUInt32LE = function readUInt32LE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + + return ((this[offset]) | + (this[offset + 1] << 8) | + (this[offset + 2] << 16)) + + (this[offset + 3] * 0x1000000) +} + +Buffer.prototype.readUInt32BE = function readUInt32BE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + + return (this[offset] * 0x1000000) + + ((this[offset + 1] << 16) | + (this[offset + 2] << 8) | + this[offset + 3]) +} + +Buffer.prototype.readIntLE = function readIntLE (offset, byteLength, noAssert) { + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) + + var val = this[offset] + var mul = 1 + var i = 0 + while (++i < byteLength && (mul *= 0x100)) { + val += this[offset + i] * mul + } + mul *= 0x80 + + if (val >= mul) val -= Math.pow(2, 8 * byteLength) + + return val +} + +Buffer.prototype.readIntBE = function readIntBE (offset, byteLength, noAssert) { + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) + + var i = byteLength + var mul = 1 + var val = this[offset + --i] + while (i > 0 && (mul *= 0x100)) { + val += this[offset + --i] * mul + } + mul *= 0x80 + + if (val >= mul) val -= Math.pow(2, 8 * byteLength) + + return val +} + +Buffer.prototype.readInt8 = function readInt8 (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 1, this.length) + if (!(this[offset] & 0x80)) return (this[offset]) + return ((0xff - this[offset] + 1) * -1) +} + +Buffer.prototype.readInt16LE = function readInt16LE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 2, this.length) + var val = this[offset] | (this[offset + 1] << 8) + return (val & 0x8000) ? val | 0xFFFF0000 : val +} + +Buffer.prototype.readInt16BE = function readInt16BE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 2, this.length) + var val = this[offset + 1] | (this[offset] << 8) + return (val & 0x8000) ? val | 0xFFFF0000 : val +} + +Buffer.prototype.readInt32LE = function readInt32LE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + + return (this[offset]) | + (this[offset + 1] << 8) | + (this[offset + 2] << 16) | + (this[offset + 3] << 24) +} + +Buffer.prototype.readInt32BE = function readInt32BE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + + return (this[offset] << 24) | + (this[offset + 1] << 16) | + (this[offset + 2] << 8) | + (this[offset + 3]) +} + +Buffer.prototype.readFloatLE = function readFloatLE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + return ieee754.read(this, offset, true, 23, 4) +} + +Buffer.prototype.readFloatBE = function readFloatBE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + return ieee754.read(this, offset, false, 23, 4) +} + +Buffer.prototype.readDoubleLE = function readDoubleLE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 8, this.length) + return ieee754.read(this, offset, true, 52, 8) +} + +Buffer.prototype.readDoubleBE = function readDoubleBE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 8, this.length) + return ieee754.read(this, offset, false, 52, 8) +} + +function checkInt (buf, value, offset, ext, max, min) { + if (!Buffer.isBuffer(buf)) throw new TypeError('"buffer" argument must be a Buffer instance') + if (value > max || value < min) throw new RangeError('"value" argument is out of bounds') + if (offset + ext > buf.length) throw new RangeError('Index out of range') +} + +Buffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) { + value = +value + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) { + var maxBytes = Math.pow(2, 8 * byteLength) - 1 + checkInt(this, value, offset, byteLength, maxBytes, 0) + } + + var mul = 1 + var i = 0 + this[offset] = value & 0xFF + while (++i < byteLength && (mul *= 0x100)) { + this[offset + i] = (value / mul) & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, noAssert) { + value = +value + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) { + var maxBytes = Math.pow(2, 8 * byteLength) - 1 + checkInt(this, value, offset, byteLength, maxBytes, 0) + } + + var i = byteLength - 1 + var mul = 1 + this[offset + i] = value & 0xFF + while (--i >= 0 && (mul *= 0x100)) { + this[offset + i] = (value / mul) & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0) + this[offset] = (value & 0xff) + return offset + 1 +} + +Buffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0) + this[offset] = (value & 0xff) + this[offset + 1] = (value >>> 8) + return offset + 2 +} + +Buffer.prototype.writeUInt16BE = function writeUInt16BE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0) + this[offset] = (value >>> 8) + this[offset + 1] = (value & 0xff) + return offset + 2 +} + +Buffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0) + this[offset + 3] = (value >>> 24) + this[offset + 2] = (value >>> 16) + this[offset + 1] = (value >>> 8) + this[offset] = (value & 0xff) + return offset + 4 +} + +Buffer.prototype.writeUInt32BE = function writeUInt32BE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0) + this[offset] = (value >>> 24) + this[offset + 1] = (value >>> 16) + this[offset + 2] = (value >>> 8) + this[offset + 3] = (value & 0xff) + return offset + 4 +} + +Buffer.prototype.writeIntLE = function writeIntLE (value, offset, byteLength, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) { + var limit = Math.pow(2, (8 * byteLength) - 1) + + checkInt(this, value, offset, byteLength, limit - 1, -limit) + } + + var i = 0 + var mul = 1 + var sub = 0 + this[offset] = value & 0xFF + while (++i < byteLength && (mul *= 0x100)) { + if (value < 0 && sub === 0 && this[offset + i - 1] !== 0) { + sub = 1 + } + this[offset + i] = ((value / mul) >> 0) - sub & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeIntBE = function writeIntBE (value, offset, byteLength, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) { + var limit = Math.pow(2, (8 * byteLength) - 1) + + checkInt(this, value, offset, byteLength, limit - 1, -limit) + } + + var i = byteLength - 1 + var mul = 1 + var sub = 0 + this[offset + i] = value & 0xFF + while (--i >= 0 && (mul *= 0x100)) { + if (value < 0 && sub === 0 && this[offset + i + 1] !== 0) { + sub = 1 + } + this[offset + i] = ((value / mul) >> 0) - sub & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeInt8 = function writeInt8 (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80) + if (value < 0) value = 0xff + value + 1 + this[offset] = (value & 0xff) + return offset + 1 +} + +Buffer.prototype.writeInt16LE = function writeInt16LE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000) + this[offset] = (value & 0xff) + this[offset + 1] = (value >>> 8) + return offset + 2 +} + +Buffer.prototype.writeInt16BE = function writeInt16BE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000) + this[offset] = (value >>> 8) + this[offset + 1] = (value & 0xff) + return offset + 2 +} + +Buffer.prototype.writeInt32LE = function writeInt32LE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000) + this[offset] = (value & 0xff) + this[offset + 1] = (value >>> 8) + this[offset + 2] = (value >>> 16) + this[offset + 3] = (value >>> 24) + return offset + 4 +} + +Buffer.prototype.writeInt32BE = function writeInt32BE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000) + if (value < 0) value = 0xffffffff + value + 1 + this[offset] = (value >>> 24) + this[offset + 1] = (value >>> 16) + this[offset + 2] = (value >>> 8) + this[offset + 3] = (value & 0xff) + return offset + 4 +} + +function checkIEEE754 (buf, value, offset, ext, max, min) { + if (offset + ext > buf.length) throw new RangeError('Index out of range') + if (offset < 0) throw new RangeError('Index out of range') +} + +function writeFloat (buf, value, offset, littleEndian, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) { + checkIEEE754(buf, value, offset, 4, 3.4028234663852886e+38, -3.4028234663852886e+38) + } + ieee754.write(buf, value, offset, littleEndian, 23, 4) + return offset + 4 +} + +Buffer.prototype.writeFloatLE = function writeFloatLE (value, offset, noAssert) { + return writeFloat(this, value, offset, true, noAssert) +} + +Buffer.prototype.writeFloatBE = function writeFloatBE (value, offset, noAssert) { + return writeFloat(this, value, offset, false, noAssert) +} + +function writeDouble (buf, value, offset, littleEndian, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) { + checkIEEE754(buf, value, offset, 8, 1.7976931348623157E+308, -1.7976931348623157E+308) + } + ieee754.write(buf, value, offset, littleEndian, 52, 8) + return offset + 8 +} + +Buffer.prototype.writeDoubleLE = function writeDoubleLE (value, offset, noAssert) { + return writeDouble(this, value, offset, true, noAssert) +} + +Buffer.prototype.writeDoubleBE = function writeDoubleBE (value, offset, noAssert) { + return writeDouble(this, value, offset, false, noAssert) +} + +// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length) +Buffer.prototype.copy = function copy (target, targetStart, start, end) { + if (!Buffer.isBuffer(target)) throw new TypeError('argument should be a Buffer') + if (!start) start = 0 + if (!end && end !== 0) end = this.length + if (targetStart >= target.length) targetStart = target.length + if (!targetStart) targetStart = 0 + if (end > 0 && end < start) end = start + + // Copy 0 bytes; we're done + if (end === start) return 0 + if (target.length === 0 || this.length === 0) return 0 + + // Fatal error conditions + if (targetStart < 0) { + throw new RangeError('targetStart out of bounds') + } + if (start < 0 || start >= this.length) throw new RangeError('Index out of range') + if (end < 0) throw new RangeError('sourceEnd out of bounds') + + // Are we oob? + if (end > this.length) end = this.length + if (target.length - targetStart < end - start) { + end = target.length - targetStart + start + } + + var len = end - start + + if (this === target && typeof Uint8Array.prototype.copyWithin === 'function') { + // Use built-in when available, missing from IE11 + this.copyWithin(targetStart, start, end) + } else if (this === target && start < targetStart && targetStart < end) { + // descending copy from end + for (var i = len - 1; i >= 0; --i) { + target[i + targetStart] = this[i + start] + } + } else { + Uint8Array.prototype.set.call( + target, + this.subarray(start, end), + targetStart + ) + } + + return len +} + +// Usage: +// buffer.fill(number[, offset[, end]]) +// buffer.fill(buffer[, offset[, end]]) +// buffer.fill(string[, offset[, end]][, encoding]) +Buffer.prototype.fill = function fill (val, start, end, encoding) { + // Handle string cases: + if (typeof val === 'string') { + if (typeof start === 'string') { + encoding = start + start = 0 + end = this.length + } else if (typeof end === 'string') { + encoding = end + end = this.length + } + if (encoding !== undefined && typeof encoding !== 'string') { + throw new TypeError('encoding must be a string') + } + if (typeof encoding === 'string' && !Buffer.isEncoding(encoding)) { + throw new TypeError('Unknown encoding: ' + encoding) + } + if (val.length === 1) { + var code = val.charCodeAt(0) + if ((encoding === 'utf8' && code < 128) || + encoding === 'latin1') { + // Fast path: If `val` fits into a single byte, use that numeric value. + val = code + } + } + } else if (typeof val === 'number') { + val = val & 255 + } + + // Invalid ranges are not set to a default, so can range check early. + if (start < 0 || this.length < start || this.length < end) { + throw new RangeError('Out of range index') + } + + if (end <= start) { + return this + } + + start = start >>> 0 + end = end === undefined ? this.length : end >>> 0 + + if (!val) val = 0 + + var i + if (typeof val === 'number') { + for (i = start; i < end; ++i) { + this[i] = val + } + } else { + var bytes = Buffer.isBuffer(val) + ? val + : Buffer.from(val, encoding) + var len = bytes.length + if (len === 0) { + throw new TypeError('The value "' + val + + '" is invalid for argument "value"') + } + for (i = 0; i < end - start; ++i) { + this[i + start] = bytes[i % len] + } + } + + return this +} + +// HELPER FUNCTIONS +// ================ + +var INVALID_BASE64_RE = /[^+/0-9A-Za-z-_]/g + +function base64clean (str) { + // Node takes equal signs as end of the Base64 encoding + str = str.split('=')[0] + // Node strips out invalid characters like \n and \t from the string, base64-js does not + str = str.trim().replace(INVALID_BASE64_RE, '') + // Node converts strings with length < 2 to '' + if (str.length < 2) return '' + // Node allows for non-padded base64 strings (missing trailing ===), base64-js does not + while (str.length % 4 !== 0) { + str = str + '=' + } + return str +} + +function toHex (n) { + if (n < 16) return '0' + n.toString(16) + return n.toString(16) +} + +function utf8ToBytes (string, units) { + units = units || Infinity + var codePoint + var length = string.length + var leadSurrogate = null + var bytes = [] + + for (var i = 0; i < length; ++i) { + codePoint = string.charCodeAt(i) + + // is surrogate component + if (codePoint > 0xD7FF && codePoint < 0xE000) { + // last char was a lead + if (!leadSurrogate) { + // no lead yet + if (codePoint > 0xDBFF) { + // unexpected trail + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + continue + } else if (i + 1 === length) { + // unpaired lead + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + continue + } + + // valid lead + leadSurrogate = codePoint + + continue + } + + // 2 leads in a row + if (codePoint < 0xDC00) { + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + leadSurrogate = codePoint + continue + } + + // valid surrogate pair + codePoint = (leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00) + 0x10000 + } else if (leadSurrogate) { + // valid bmp char, but last char was a lead + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + } + + leadSurrogate = null + + // encode utf8 + if (codePoint < 0x80) { + if ((units -= 1) < 0) break + bytes.push(codePoint) + } else if (codePoint < 0x800) { + if ((units -= 2) < 0) break + bytes.push( + codePoint >> 0x6 | 0xC0, + codePoint & 0x3F | 0x80 + ) + } else if (codePoint < 0x10000) { + if ((units -= 3) < 0) break + bytes.push( + codePoint >> 0xC | 0xE0, + codePoint >> 0x6 & 0x3F | 0x80, + codePoint & 0x3F | 0x80 + ) + } else if (codePoint < 0x110000) { + if ((units -= 4) < 0) break + bytes.push( + codePoint >> 0x12 | 0xF0, + codePoint >> 0xC & 0x3F | 0x80, + codePoint >> 0x6 & 0x3F | 0x80, + codePoint & 0x3F | 0x80 + ) + } else { + throw new Error('Invalid code point') + } + } + + return bytes +} + +function asciiToBytes (str) { + var byteArray = [] + for (var i = 0; i < str.length; ++i) { + // Node's code seems to be doing this and not & 0x7F.. + byteArray.push(str.charCodeAt(i) & 0xFF) + } + return byteArray +} + +function utf16leToBytes (str, units) { + var c, hi, lo + var byteArray = [] + for (var i = 0; i < str.length; ++i) { + if ((units -= 2) < 0) break + + c = str.charCodeAt(i) + hi = c >> 8 + lo = c % 256 + byteArray.push(lo) + byteArray.push(hi) + } + + return byteArray +} + +function base64ToBytes (str) { + return base64.toByteArray(base64clean(str)) +} + +function blitBuffer (src, dst, offset, length) { + for (var i = 0; i < length; ++i) { + if ((i + offset >= dst.length) || (i >= src.length)) break + dst[i + offset] = src[i] + } + return i +} + +// ArrayBuffer or Uint8Array objects from other contexts (i.e. iframes) do not pass +// the `instanceof` check but they should be treated as of that type. +// See: https://github.com/feross/buffer/issues/166 +function isInstance (obj, type) { + return obj instanceof type || + (obj != null && obj.constructor != null && obj.constructor.name != null && + obj.constructor.name === type.name) +} +function numberIsNaN (obj) { + // For IE11 support + return obj !== obj // eslint-disable-line no-self-compare +} + +}).call(this,require("buffer").Buffer) +},{"base64-js":39,"buffer":43,"ieee754":55}],44:[function(require,module,exports){ +(function (Buffer){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// NOTE: These type checking functions intentionally don't use `instanceof` +// because it is fragile and can be easily faked with `Object.create()`. + +function isArray(arg) { + if (Array.isArray) { + return Array.isArray(arg); + } + return objectToString(arg) === '[object Array]'; +} +exports.isArray = isArray; + +function isBoolean(arg) { + return typeof arg === 'boolean'; +} +exports.isBoolean = isBoolean; + +function isNull(arg) { + return arg === null; +} +exports.isNull = isNull; + +function isNullOrUndefined(arg) { + return arg == null; +} +exports.isNullOrUndefined = isNullOrUndefined; + +function isNumber(arg) { + return typeof arg === 'number'; +} +exports.isNumber = isNumber; + +function isString(arg) { + return typeof arg === 'string'; +} +exports.isString = isString; + +function isSymbol(arg) { + return typeof arg === 'symbol'; +} +exports.isSymbol = isSymbol; + +function isUndefined(arg) { + return arg === void 0; +} +exports.isUndefined = isUndefined; + +function isRegExp(re) { + return objectToString(re) === '[object RegExp]'; +} +exports.isRegExp = isRegExp; + +function isObject(arg) { + return typeof arg === 'object' && arg !== null; +} +exports.isObject = isObject; + +function isDate(d) { + return objectToString(d) === '[object Date]'; +} +exports.isDate = isDate; + +function isError(e) { + return (objectToString(e) === '[object Error]' || e instanceof Error); +} +exports.isError = isError; + +function isFunction(arg) { + return typeof arg === 'function'; +} +exports.isFunction = isFunction; + +function isPrimitive(arg) { + return arg === null || + typeof arg === 'boolean' || + typeof arg === 'number' || + typeof arg === 'string' || + typeof arg === 'symbol' || // ES6 symbol + typeof arg === 'undefined'; +} +exports.isPrimitive = isPrimitive; + +exports.isBuffer = Buffer.isBuffer; + +function objectToString(o) { + return Object.prototype.toString.call(o); +} + +}).call(this,{"isBuffer":require("../../is-buffer/index.js")}) +},{"../../is-buffer/index.js":57}],45:[function(require,module,exports){ +(function (process){ +"use strict"; + +function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } + +/* eslint-env browser */ + +/** + * This is the web browser implementation of `debug()`. + */ +exports.log = log; +exports.formatArgs = formatArgs; +exports.save = save; +exports.load = load; +exports.useColors = useColors; +exports.storage = localstorage(); +/** + * Colors. + */ + +exports.colors = ['#0000CC', '#0000FF', '#0033CC', '#0033FF', '#0066CC', '#0066FF', '#0099CC', '#0099FF', '#00CC00', '#00CC33', '#00CC66', '#00CC99', '#00CCCC', '#00CCFF', '#3300CC', '#3300FF', '#3333CC', '#3333FF', '#3366CC', '#3366FF', '#3399CC', '#3399FF', '#33CC00', '#33CC33', '#33CC66', '#33CC99', '#33CCCC', '#33CCFF', '#6600CC', '#6600FF', '#6633CC', '#6633FF', '#66CC00', '#66CC33', '#9900CC', '#9900FF', '#9933CC', '#9933FF', '#99CC00', '#99CC33', '#CC0000', '#CC0033', '#CC0066', '#CC0099', '#CC00CC', '#CC00FF', '#CC3300', '#CC3333', '#CC3366', '#CC3399', '#CC33CC', '#CC33FF', '#CC6600', '#CC6633', '#CC9900', '#CC9933', '#CCCC00', '#CCCC33', '#FF0000', '#FF0033', '#FF0066', '#FF0099', '#FF00CC', '#FF00FF', '#FF3300', '#FF3333', '#FF3366', '#FF3399', '#FF33CC', '#FF33FF', '#FF6600', '#FF6633', '#FF9900', '#FF9933', '#FFCC00', '#FFCC33']; +/** + * Currently only WebKit-based Web Inspectors, Firefox >= v31, + * and the Firebug extension (any Firefox version) are known + * to support "%c" CSS customizations. + * + * TODO: add a `localStorage` variable to explicitly enable/disable colors + */ +// eslint-disable-next-line complexity + +function useColors() { + // NB: In an Electron preload script, document will be defined but not fully + // initialized. Since we know we're in Chrome, we'll just detect this case + // explicitly + if (typeof window !== 'undefined' && window.process && (window.process.type === 'renderer' || window.process.__nwjs)) { + return true; + } // Internet Explorer and Edge do not support colors. + + + if (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) { + return false; + } // Is webkit? http://stackoverflow.com/a/16459606/376773 + // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632 + + + return typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance || // Is firebug? http://stackoverflow.com/a/398120/376773 + typeof window !== 'undefined' && window.console && (window.console.firebug || window.console.exception && window.console.table) || // Is firefox >= v31? + // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages + typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31 || // Double check webkit in userAgent just in case we are in a worker + typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/); +} +/** + * Colorize log arguments if enabled. + * + * @api public + */ + + +function formatArgs(args) { + args[0] = (this.useColors ? '%c' : '') + this.namespace + (this.useColors ? ' %c' : ' ') + args[0] + (this.useColors ? '%c ' : ' ') + '+' + module.exports.humanize(this.diff); + + if (!this.useColors) { + return; + } + + var c = 'color: ' + this.color; + args.splice(1, 0, c, 'color: inherit'); // The final "%c" is somewhat tricky, because there could be other + // arguments passed either before or after the %c, so we need to + // figure out the correct index to insert the CSS into + + var index = 0; + var lastC = 0; + args[0].replace(/%[a-zA-Z%]/g, function (match) { + if (match === '%%') { + return; + } + + index++; + + if (match === '%c') { + // We only are interested in the *last* %c + // (the user may have provided their own) + lastC = index; + } + }); + args.splice(lastC, 0, c); +} +/** + * Invokes `console.log()` when available. + * No-op when `console.log` is not a "function". + * + * @api public + */ + + +function log() { + var _console; + + // This hackery is required for IE8/9, where + // the `console.log` function doesn't have 'apply' + return (typeof console === "undefined" ? "undefined" : _typeof(console)) === 'object' && console.log && (_console = console).log.apply(_console, arguments); +} +/** + * Save `namespaces`. + * + * @param {String} namespaces + * @api private + */ + + +function save(namespaces) { + try { + if (namespaces) { + exports.storage.setItem('debug', namespaces); + } else { + exports.storage.removeItem('debug'); + } + } catch (error) {// Swallow + // XXX (@Qix-) should we be logging these? + } +} +/** + * Load `namespaces`. + * + * @return {String} returns the previously persisted debug modes + * @api private + */ + + +function load() { + var r; + + try { + r = exports.storage.getItem('debug'); + } catch (error) {} // Swallow + // XXX (@Qix-) should we be logging these? + // If debug isn't set in LS, and we're in Electron, try to load $DEBUG + + + if (!r && typeof process !== 'undefined' && 'env' in process) { + r = process.env.DEBUG; + } + + return r; +} +/** + * Localstorage attempts to return the localstorage. + * + * This is necessary because safari throws + * when a user disables cookies/localstorage + * and you attempt to access it. + * + * @return {LocalStorage} + * @api private + */ + + +function localstorage() { + try { + // TVMLKit (Apple TV JS Runtime) does not have a window object, just localStorage in the global context + // The Browser also has localStorage in the global context. + return localStorage; + } catch (error) {// Swallow + // XXX (@Qix-) should we be logging these? + } +} + +module.exports = require('./common')(exports); +var formatters = module.exports.formatters; +/** + * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. + */ + +formatters.j = function (v) { + try { + return JSON.stringify(v); + } catch (error) { + return '[UnexpectedJSONParseError]: ' + error.message; + } +}; + + +}).call(this,require('_process')) +},{"./common":46,"_process":69}],46:[function(require,module,exports){ +"use strict"; + +/** + * This is the common logic for both the Node.js and web browser + * implementations of `debug()`. + */ +function setup(env) { + createDebug.debug = createDebug; + createDebug.default = createDebug; + createDebug.coerce = coerce; + createDebug.disable = disable; + createDebug.enable = enable; + createDebug.enabled = enabled; + createDebug.humanize = require('ms'); + Object.keys(env).forEach(function (key) { + createDebug[key] = env[key]; + }); /** - * Expose mocha. - */ - - var Mocha = window.Mocha = require('mocha'), - mocha = window.mocha = new Mocha({ reporter: 'html' }); + * Active `debug` instances. + */ + createDebug.instances = []; /** - * Override ui to ensure that the ui functions are initialized. - * Normally this would happen in Mocha.prototype.loadFiles. - */ + * The currently active debug mode names, and names to skip. + */ - mocha.ui = function(ui){ - Mocha.prototype.ui.call(this, ui); - this.suite.emit('pre-require', window, null, this); - return this; + createDebug.names = []; + createDebug.skips = []; + /** + * Map of special "%n" handling functions, for the debug "format" argument. + * + * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N". + */ + + createDebug.formatters = {}; + /** + * Selects a color for a debug namespace + * @param {String} namespace The namespace string for the for the debug instance to be colored + * @return {Number|String} An ANSI color code for the given namespace + * @api private + */ + + function selectColor(namespace) { + var hash = 0; + + for (var i = 0; i < namespace.length; i++) { + hash = (hash << 5) - hash + namespace.charCodeAt(i); + hash |= 0; // Convert to 32bit integer + } + + return createDebug.colors[Math.abs(hash) % createDebug.colors.length]; + } + + createDebug.selectColor = selectColor; + /** + * Create a debugger with the given `namespace`. + * + * @param {String} namespace + * @return {Function} + * @api public + */ + + function createDebug(namespace) { + var prevTime; + + function debug() { + // Disabled? + if (!debug.enabled) { + return; + } + + for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + var self = debug; // Set `diff` timestamp + + var curr = Number(new Date()); + var ms = curr - (prevTime || curr); + self.diff = ms; + self.prev = prevTime; + self.curr = curr; + prevTime = curr; + args[0] = createDebug.coerce(args[0]); + + if (typeof args[0] !== 'string') { + // Anything else let's inspect with %O + args.unshift('%O'); + } // Apply any `formatters` transformations + + + var index = 0; + args[0] = args[0].replace(/%([a-zA-Z%])/g, function (match, format) { + // If we encounter an escaped % then don't increase the array index + if (match === '%%') { + return match; + } + + index++; + var formatter = createDebug.formatters[format]; + + if (typeof formatter === 'function') { + var val = args[index]; + match = formatter.call(self, val); // Now we need to remove `args[index]` since it's inlined in the `format` + + args.splice(index, 1); + index--; + } + + return match; + }); // Apply env-specific formatting (colors, etc.) + + createDebug.formatArgs.call(self, args); + var logFn = self.log || createDebug.log; + logFn.apply(self, args); + } + + debug.namespace = namespace; + debug.enabled = createDebug.enabled(namespace); + debug.useColors = createDebug.useColors(); + debug.color = selectColor(namespace); + debug.destroy = destroy; + debug.extend = extend; // Debug.formatArgs = formatArgs; + // debug.rawLog = rawLog; + // env-specific initialization logic for debug instances + + if (typeof createDebug.init === 'function') { + createDebug.init(debug); + } + + createDebug.instances.push(debug); + return debug; + } + + function destroy() { + var index = createDebug.instances.indexOf(this); + + if (index !== -1) { + createDebug.instances.splice(index, 1); + return true; + } + + return false; + } + + function extend(namespace, delimiter) { + return createDebug(this.namespace + (typeof delimiter === 'undefined' ? ':' : delimiter) + namespace); + } + /** + * Enables a debug mode by namespaces. This can include modes + * separated by a colon and wildcards. + * + * @param {String} namespaces + * @api public + */ + + + function enable(namespaces) { + createDebug.save(namespaces); + createDebug.names = []; + createDebug.skips = []; + var i; + var split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/); + var len = split.length; + + for (i = 0; i < len; i++) { + if (!split[i]) { + // ignore empty strings + continue; + } + + namespaces = split[i].replace(/\*/g, '.*?'); + + if (namespaces[0] === '-') { + createDebug.skips.push(new RegExp('^' + namespaces.substr(1) + '$')); + } else { + createDebug.names.push(new RegExp('^' + namespaces + '$')); + } + } + + for (i = 0; i < createDebug.instances.length; i++) { + var instance = createDebug.instances[i]; + instance.enabled = createDebug.enabled(instance.namespace); + } + } + /** + * Disable debug output. + * + * @api public + */ + + + function disable() { + createDebug.enable(''); + } + /** + * Returns true if the given mode name is enabled, false otherwise. + * + * @param {String} name + * @return {Boolean} + * @api public + */ + + + function enabled(name) { + if (name[name.length - 1] === '*') { + return true; + } + + var i; + var len; + + for (i = 0, len = createDebug.skips.length; i < len; i++) { + if (createDebug.skips[i].test(name)) { + return false; + } + } + + for (i = 0, len = createDebug.names.length; i < len; i++) { + if (createDebug.names[i].test(name)) { + return true; + } + } + + return false; + } + /** + * Coerce `val`. + * + * @param {Mixed} val + * @return {Mixed} + * @api private + */ + + + function coerce(val) { + if (val instanceof Error) { + return val.stack || val.message; + } + + return val; + } + + createDebug.enable(createDebug.load()); + return createDebug; +} + +module.exports = setup; + + +},{"ms":60}],47:[function(require,module,exports){ +'use strict'; + +var keys = require('object-keys'); +var hasSymbols = typeof Symbol === 'function' && typeof Symbol('foo') === 'symbol'; + +var toStr = Object.prototype.toString; +var concat = Array.prototype.concat; +var origDefineProperty = Object.defineProperty; + +var isFunction = function (fn) { + return typeof fn === 'function' && toStr.call(fn) === '[object Function]'; +}; + +var arePropertyDescriptorsSupported = function () { + var obj = {}; + try { + origDefineProperty(obj, 'x', { enumerable: false, value: obj }); + // eslint-disable-next-line no-unused-vars, no-restricted-syntax + for (var _ in obj) { // jscs:ignore disallowUnusedVariables + return false; + } + return obj.x === obj; + } catch (e) { /* this is IE 8. */ + return false; + } +}; +var supportsDescriptors = origDefineProperty && arePropertyDescriptorsSupported(); + +var defineProperty = function (object, name, value, predicate) { + if (name in object && (!isFunction(predicate) || !predicate())) { + return; + } + if (supportsDescriptors) { + origDefineProperty(object, name, { + configurable: true, + enumerable: false, + value: value, + writable: true + }); + } else { + object[name] = value; + } +}; + +var defineProperties = function (object, map) { + var predicates = arguments.length > 2 ? arguments[2] : {}; + var props = keys(map); + if (hasSymbols) { + props = concat.call(props, Object.getOwnPropertySymbols(map)); + } + for (var i = 0; i < props.length; i += 1) { + defineProperty(object, props[i], map[props[i]], predicates[props[i]]); + } +}; + +defineProperties.supportsDescriptors = !!supportsDescriptors; + +module.exports = defineProperties; + +},{"object-keys":62}],48:[function(require,module,exports){ +/*! + + diff v3.5.0 + +Software License Agreement (BSD License) + +Copyright (c) 2009-2015, Kevin Decker + +All rights reserved. + +Redistribution and use of this software 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. + +* Neither the name of Kevin Decker nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission. + +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 THE COPYRIGHT OWNER OR +CONTRIBUTORS 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. +@license +*/ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(false) + define([], factory); + else if(typeof exports === 'object') + exports["JsDiff"] = factory(); + else + root["JsDiff"] = factory(); +})(this, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; + +/******/ // The require function +/******/ function __webpack_require__(moduleId) { + +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) +/******/ return installedModules[moduleId].exports; + +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ exports: {}, +/******/ id: moduleId, +/******/ loaded: false +/******/ }; + +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); + +/******/ // Flag the module as loaded +/******/ module.loaded = true; + +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } + + +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; + +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; + +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; + +/******/ // Load entry module and return exports +/******/ return __webpack_require__(0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, exports, __webpack_require__) { + + /*istanbul ignore start*/'use strict'; + + exports.__esModule = true; + exports.canonicalize = exports.convertChangesToXML = exports.convertChangesToDMP = exports.merge = exports.parsePatch = exports.applyPatches = exports.applyPatch = exports.createPatch = exports.createTwoFilesPatch = exports.structuredPatch = exports.diffArrays = exports.diffJson = exports.diffCss = exports.diffSentences = exports.diffTrimmedLines = exports.diffLines = exports.diffWordsWithSpace = exports.diffWords = exports.diffChars = exports.Diff = undefined; + + /*istanbul ignore end*/var /*istanbul ignore start*/_base = __webpack_require__(1) /*istanbul ignore end*/; + + /*istanbul ignore start*/var _base2 = _interopRequireDefault(_base); + + /*istanbul ignore end*/var /*istanbul ignore start*/_character = __webpack_require__(2) /*istanbul ignore end*/; + + var /*istanbul ignore start*/_word = __webpack_require__(3) /*istanbul ignore end*/; + + var /*istanbul ignore start*/_line = __webpack_require__(5) /*istanbul ignore end*/; + + var /*istanbul ignore start*/_sentence = __webpack_require__(6) /*istanbul ignore end*/; + + var /*istanbul ignore start*/_css = __webpack_require__(7) /*istanbul ignore end*/; + + var /*istanbul ignore start*/_json = __webpack_require__(8) /*istanbul ignore end*/; + + var /*istanbul ignore start*/_array = __webpack_require__(9) /*istanbul ignore end*/; + + var /*istanbul ignore start*/_apply = __webpack_require__(10) /*istanbul ignore end*/; + + var /*istanbul ignore start*/_parse = __webpack_require__(11) /*istanbul ignore end*/; + + var /*istanbul ignore start*/_merge = __webpack_require__(13) /*istanbul ignore end*/; + + var /*istanbul ignore start*/_create = __webpack_require__(14) /*istanbul ignore end*/; + + var /*istanbul ignore start*/_dmp = __webpack_require__(16) /*istanbul ignore end*/; + + var /*istanbul ignore start*/_xml = __webpack_require__(17) /*istanbul ignore end*/; + + /*istanbul ignore start*/function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + /* See LICENSE file for terms of use */ + + /* + * Text diff implementation. + * + * This library supports the following APIS: + * JsDiff.diffChars: Character by character diff + * JsDiff.diffWords: Word (as defined by \b regex) diff which ignores whitespace + * JsDiff.diffLines: Line based diff + * + * JsDiff.diffCss: Diff targeted at CSS content + * + * These methods are based on the implementation proposed in + * "An O(ND) Difference Algorithm and its Variations" (Myers, 1986). + * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927 + */ + exports. /*istanbul ignore end*/Diff = _base2['default']; + /*istanbul ignore start*/exports. /*istanbul ignore end*/diffChars = _character.diffChars; + /*istanbul ignore start*/exports. /*istanbul ignore end*/diffWords = _word.diffWords; + /*istanbul ignore start*/exports. /*istanbul ignore end*/diffWordsWithSpace = _word.diffWordsWithSpace; + /*istanbul ignore start*/exports. /*istanbul ignore end*/diffLines = _line.diffLines; + /*istanbul ignore start*/exports. /*istanbul ignore end*/diffTrimmedLines = _line.diffTrimmedLines; + /*istanbul ignore start*/exports. /*istanbul ignore end*/diffSentences = _sentence.diffSentences; + /*istanbul ignore start*/exports. /*istanbul ignore end*/diffCss = _css.diffCss; + /*istanbul ignore start*/exports. /*istanbul ignore end*/diffJson = _json.diffJson; + /*istanbul ignore start*/exports. /*istanbul ignore end*/diffArrays = _array.diffArrays; + /*istanbul ignore start*/exports. /*istanbul ignore end*/structuredPatch = _create.structuredPatch; + /*istanbul ignore start*/exports. /*istanbul ignore end*/createTwoFilesPatch = _create.createTwoFilesPatch; + /*istanbul ignore start*/exports. /*istanbul ignore end*/createPatch = _create.createPatch; + /*istanbul ignore start*/exports. /*istanbul ignore end*/applyPatch = _apply.applyPatch; + /*istanbul ignore start*/exports. /*istanbul ignore end*/applyPatches = _apply.applyPatches; + /*istanbul ignore start*/exports. /*istanbul ignore end*/parsePatch = _parse.parsePatch; + /*istanbul ignore start*/exports. /*istanbul ignore end*/merge = _merge.merge; + /*istanbul ignore start*/exports. /*istanbul ignore end*/convertChangesToDMP = _dmp.convertChangesToDMP; + /*istanbul ignore start*/exports. /*istanbul ignore end*/convertChangesToXML = _xml.convertChangesToXML; + /*istanbul ignore start*/exports. /*istanbul ignore end*/canonicalize = _json.canonicalize; + + + +/***/ }), +/* 1 */ +/***/ (function(module, exports) { + + /*istanbul ignore start*/'use strict'; + + exports.__esModule = true; + exports['default'] = /*istanbul ignore end*/Diff; + function Diff() {} + + Diff.prototype = { + /*istanbul ignore start*/ /*istanbul ignore end*/diff: function diff(oldString, newString) { + /*istanbul ignore start*/var /*istanbul ignore end*/options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + + var callback = options.callback; + if (typeof options === 'function') { + callback = options; + options = {}; + } + this.options = options; + + var self = this; + + function done(value) { + if (callback) { + setTimeout(function () { + callback(undefined, value); + }, 0); + return true; + } else { + return value; + } + } + + // Allow subclasses to massage the input prior to running + oldString = this.castInput(oldString); + newString = this.castInput(newString); + + oldString = this.removeEmpty(this.tokenize(oldString)); + newString = this.removeEmpty(this.tokenize(newString)); + + var newLen = newString.length, + oldLen = oldString.length; + var editLength = 1; + var maxEditLength = newLen + oldLen; + var bestPath = [{ newPos: -1, components: [] }]; + + // Seed editLength = 0, i.e. the content starts with the same values + var oldPos = this.extractCommon(bestPath[0], newString, oldString, 0); + if (bestPath[0].newPos + 1 >= newLen && oldPos + 1 >= oldLen) { + // Identity per the equality and tokenizer + return done([{ value: this.join(newString), count: newString.length }]); + } + + // Main worker method. checks all permutations of a given edit length for acceptance. + function execEditLength() { + for (var diagonalPath = -1 * editLength; diagonalPath <= editLength; diagonalPath += 2) { + var basePath = /*istanbul ignore start*/void 0 /*istanbul ignore end*/; + var addPath = bestPath[diagonalPath - 1], + removePath = bestPath[diagonalPath + 1], + _oldPos = (removePath ? removePath.newPos : 0) - diagonalPath; + if (addPath) { + // No one else is going to attempt to use this value, clear it + bestPath[diagonalPath - 1] = undefined; + } + + var canAdd = addPath && addPath.newPos + 1 < newLen, + canRemove = removePath && 0 <= _oldPos && _oldPos < oldLen; + if (!canAdd && !canRemove) { + // If this path is a terminal then prune + bestPath[diagonalPath] = undefined; + continue; + } + + // Select the diagonal that we want to branch from. We select the prior + // path whose position in the new string is the farthest from the origin + // and does not pass the bounds of the diff graph + if (!canAdd || canRemove && addPath.newPos < removePath.newPos) { + basePath = clonePath(removePath); + self.pushComponent(basePath.components, undefined, true); + } else { + basePath = addPath; // No need to clone, we've pulled it from the list + basePath.newPos++; + self.pushComponent(basePath.components, true, undefined); + } + + _oldPos = self.extractCommon(basePath, newString, oldString, diagonalPath); + + // If we have hit the end of both strings, then we are done + if (basePath.newPos + 1 >= newLen && _oldPos + 1 >= oldLen) { + return done(buildValues(self, basePath.components, newString, oldString, self.useLongestToken)); + } else { + // Otherwise track this path as a potential candidate and continue. + bestPath[diagonalPath] = basePath; + } + } + + editLength++; + } + + // Performs the length of edit iteration. Is a bit fugly as this has to support the + // sync and async mode which is never fun. Loops over execEditLength until a value + // is produced. + if (callback) { + (function exec() { + setTimeout(function () { + // This should not happen, but we want to be safe. + /* istanbul ignore next */ + if (editLength > maxEditLength) { + return callback(); + } + + if (!execEditLength()) { + exec(); + } + }, 0); + })(); + } else { + while (editLength <= maxEditLength) { + var ret = execEditLength(); + if (ret) { + return ret; + } + } + } + }, + /*istanbul ignore start*/ /*istanbul ignore end*/pushComponent: function pushComponent(components, added, removed) { + var last = components[components.length - 1]; + if (last && last.added === added && last.removed === removed) { + // We need to clone here as the component clone operation is just + // as shallow array clone + components[components.length - 1] = { count: last.count + 1, added: added, removed: removed }; + } else { + components.push({ count: 1, added: added, removed: removed }); + } + }, + /*istanbul ignore start*/ /*istanbul ignore end*/extractCommon: function extractCommon(basePath, newString, oldString, diagonalPath) { + var newLen = newString.length, + oldLen = oldString.length, + newPos = basePath.newPos, + oldPos = newPos - diagonalPath, + commonCount = 0; + while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(newString[newPos + 1], oldString[oldPos + 1])) { + newPos++; + oldPos++; + commonCount++; + } + + if (commonCount) { + basePath.components.push({ count: commonCount }); + } + + basePath.newPos = newPos; + return oldPos; + }, + /*istanbul ignore start*/ /*istanbul ignore end*/equals: function equals(left, right) { + if (this.options.comparator) { + return this.options.comparator(left, right); + } else { + return left === right || this.options.ignoreCase && left.toLowerCase() === right.toLowerCase(); + } + }, + /*istanbul ignore start*/ /*istanbul ignore end*/removeEmpty: function removeEmpty(array) { + var ret = []; + for (var i = 0; i < array.length; i++) { + if (array[i]) { + ret.push(array[i]); + } + } + return ret; + }, + /*istanbul ignore start*/ /*istanbul ignore end*/castInput: function castInput(value) { + return value; + }, + /*istanbul ignore start*/ /*istanbul ignore end*/tokenize: function tokenize(value) { + return value.split(''); + }, + /*istanbul ignore start*/ /*istanbul ignore end*/join: function join(chars) { + return chars.join(''); + } + }; + + function buildValues(diff, components, newString, oldString, useLongestToken) { + var componentPos = 0, + componentLen = components.length, + newPos = 0, + oldPos = 0; + + for (; componentPos < componentLen; componentPos++) { + var component = components[componentPos]; + if (!component.removed) { + if (!component.added && useLongestToken) { + var value = newString.slice(newPos, newPos + component.count); + value = value.map(function (value, i) { + var oldValue = oldString[oldPos + i]; + return oldValue.length > value.length ? oldValue : value; + }); + + component.value = diff.join(value); + } else { + component.value = diff.join(newString.slice(newPos, newPos + component.count)); + } + newPos += component.count; + + // Common case + if (!component.added) { + oldPos += component.count; + } + } else { + component.value = diff.join(oldString.slice(oldPos, oldPos + component.count)); + oldPos += component.count; + + // Reverse add and remove so removes are output first to match common convention + // The diffing algorithm is tied to add then remove output and this is the simplest + // route to get the desired output with minimal overhead. + if (componentPos && components[componentPos - 1].added) { + var tmp = components[componentPos - 1]; + components[componentPos - 1] = components[componentPos]; + components[componentPos] = tmp; + } + } + } + + // Special case handle for when one terminal is ignored (i.e. whitespace). + // For this case we merge the terminal into the prior string and drop the change. + // This is only available for string mode. + var lastComponent = components[componentLen - 1]; + if (componentLen > 1 && typeof lastComponent.value === 'string' && (lastComponent.added || lastComponent.removed) && diff.equals('', lastComponent.value)) { + components[componentLen - 2].value += lastComponent.value; + components.pop(); + } + + return components; + } + + function clonePath(path) { + return { newPos: path.newPos, components: path.components.slice(0) }; + } + + + +/***/ }), +/* 2 */ +/***/ (function(module, exports, __webpack_require__) { + + /*istanbul ignore start*/'use strict'; + + exports.__esModule = true; + exports.characterDiff = undefined; + exports. /*istanbul ignore end*/diffChars = diffChars; + + var /*istanbul ignore start*/_base = __webpack_require__(1) /*istanbul ignore end*/; + + /*istanbul ignore start*/var _base2 = _interopRequireDefault(_base); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + /*istanbul ignore end*/var characterDiff = /*istanbul ignore start*/exports. /*istanbul ignore end*/characterDiff = new /*istanbul ignore start*/_base2['default'] /*istanbul ignore end*/(); + function diffChars(oldStr, newStr, options) { + return characterDiff.diff(oldStr, newStr, options); + } + + + +/***/ }), +/* 3 */ +/***/ (function(module, exports, __webpack_require__) { + + /*istanbul ignore start*/'use strict'; + + exports.__esModule = true; + exports.wordDiff = undefined; + exports. /*istanbul ignore end*/diffWords = diffWords; + /*istanbul ignore start*/exports. /*istanbul ignore end*/diffWordsWithSpace = diffWordsWithSpace; + + var /*istanbul ignore start*/_base = __webpack_require__(1) /*istanbul ignore end*/; + + /*istanbul ignore start*/var _base2 = _interopRequireDefault(_base); + + /*istanbul ignore end*/var /*istanbul ignore start*/_params = __webpack_require__(4) /*istanbul ignore end*/; + + /*istanbul ignore start*/function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + /*istanbul ignore end*/ // Based on https://en.wikipedia.org/wiki/Latin_script_in_Unicode + // + // Ranges and exceptions: + // Latin-1 Supplement, 0080โ€“00FF + // - U+00D7 ร— Multiplication sign + // - U+00F7 รท Division sign + // Latin Extended-A, 0100โ€“017F + // Latin Extended-B, 0180โ€“024F + // IPA Extensions, 0250โ€“02AF + // Spacing Modifier Letters, 02B0โ€“02FF + // - U+02C7 ห‡ ˇ Caron + // - U+02D8 ห˜ ˘ Breve + // - U+02D9 ห™ ˙ Dot Above + // - U+02DA หš ˚ Ring Above + // - U+02DB ห› ˛ Ogonek + // - U+02DC หœ ˜ Small Tilde + // - U+02DD ห ˝ Double Acute Accent + // Latin Extended Additional, 1E00โ€“1EFF + var extendedWordChars = /^[A-Za-z\xC0-\u02C6\u02C8-\u02D7\u02DE-\u02FF\u1E00-\u1EFF]+$/; + + var reWhitespace = /\S/; + + var wordDiff = /*istanbul ignore start*/exports. /*istanbul ignore end*/wordDiff = new /*istanbul ignore start*/_base2['default'] /*istanbul ignore end*/(); + wordDiff.equals = function (left, right) { + if (this.options.ignoreCase) { + left = left.toLowerCase(); + right = right.toLowerCase(); + } + return left === right || this.options.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right); + }; + wordDiff.tokenize = function (value) { + var tokens = value.split(/(\s+|\b)/); + + // Join the boundary splits that we do not consider to be boundaries. This is primarily the extended Latin character set. + for (var i = 0; i < tokens.length - 1; i++) { + // If we have an empty string in the next field and we have only word chars before and after, merge + if (!tokens[i + 1] && tokens[i + 2] && extendedWordChars.test(tokens[i]) && extendedWordChars.test(tokens[i + 2])) { + tokens[i] += tokens[i + 2]; + tokens.splice(i + 1, 2); + i--; + } + } + + return tokens; + }; + + function diffWords(oldStr, newStr, options) { + options = /*istanbul ignore start*/(0, _params.generateOptions) /*istanbul ignore end*/(options, { ignoreWhitespace: true }); + return wordDiff.diff(oldStr, newStr, options); + } + + function diffWordsWithSpace(oldStr, newStr, options) { + return wordDiff.diff(oldStr, newStr, options); + } + + + +/***/ }), +/* 4 */ +/***/ (function(module, exports) { + + /*istanbul ignore start*/'use strict'; + + exports.__esModule = true; + exports. /*istanbul ignore end*/generateOptions = generateOptions; + function generateOptions(options, defaults) { + if (typeof options === 'function') { + defaults.callback = options; + } else if (options) { + for (var name in options) { + /* istanbul ignore else */ + if (options.hasOwnProperty(name)) { + defaults[name] = options[name]; + } + } + } + return defaults; + } + + + +/***/ }), +/* 5 */ +/***/ (function(module, exports, __webpack_require__) { + + /*istanbul ignore start*/'use strict'; + + exports.__esModule = true; + exports.lineDiff = undefined; + exports. /*istanbul ignore end*/diffLines = diffLines; + /*istanbul ignore start*/exports. /*istanbul ignore end*/diffTrimmedLines = diffTrimmedLines; + + var /*istanbul ignore start*/_base = __webpack_require__(1) /*istanbul ignore end*/; + + /*istanbul ignore start*/var _base2 = _interopRequireDefault(_base); + + /*istanbul ignore end*/var /*istanbul ignore start*/_params = __webpack_require__(4) /*istanbul ignore end*/; + + /*istanbul ignore start*/function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + /*istanbul ignore end*/var lineDiff = /*istanbul ignore start*/exports. /*istanbul ignore end*/lineDiff = new /*istanbul ignore start*/_base2['default'] /*istanbul ignore end*/(); + lineDiff.tokenize = function (value) { + var retLines = [], + linesAndNewlines = value.split(/(\n|\r\n)/); + + // Ignore the final empty token that occurs if the string ends with a new line + if (!linesAndNewlines[linesAndNewlines.length - 1]) { + linesAndNewlines.pop(); + } + + // Merge the content and line separators into single tokens + for (var i = 0; i < linesAndNewlines.length; i++) { + var line = linesAndNewlines[i]; + + if (i % 2 && !this.options.newlineIsToken) { + retLines[retLines.length - 1] += line; + } else { + if (this.options.ignoreWhitespace) { + line = line.trim(); + } + retLines.push(line); + } + } + + return retLines; + }; + + function diffLines(oldStr, newStr, callback) { + return lineDiff.diff(oldStr, newStr, callback); + } + function diffTrimmedLines(oldStr, newStr, callback) { + var options = /*istanbul ignore start*/(0, _params.generateOptions) /*istanbul ignore end*/(callback, { ignoreWhitespace: true }); + return lineDiff.diff(oldStr, newStr, options); + } + + + +/***/ }), +/* 6 */ +/***/ (function(module, exports, __webpack_require__) { + + /*istanbul ignore start*/'use strict'; + + exports.__esModule = true; + exports.sentenceDiff = undefined; + exports. /*istanbul ignore end*/diffSentences = diffSentences; + + var /*istanbul ignore start*/_base = __webpack_require__(1) /*istanbul ignore end*/; + + /*istanbul ignore start*/var _base2 = _interopRequireDefault(_base); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + /*istanbul ignore end*/var sentenceDiff = /*istanbul ignore start*/exports. /*istanbul ignore end*/sentenceDiff = new /*istanbul ignore start*/_base2['default'] /*istanbul ignore end*/(); + sentenceDiff.tokenize = function (value) { + return value.split(/(\S.+?[.!?])(?=\s+|$)/); + }; + + function diffSentences(oldStr, newStr, callback) { + return sentenceDiff.diff(oldStr, newStr, callback); + } + + + +/***/ }), +/* 7 */ +/***/ (function(module, exports, __webpack_require__) { + + /*istanbul ignore start*/'use strict'; + + exports.__esModule = true; + exports.cssDiff = undefined; + exports. /*istanbul ignore end*/diffCss = diffCss; + + var /*istanbul ignore start*/_base = __webpack_require__(1) /*istanbul ignore end*/; + + /*istanbul ignore start*/var _base2 = _interopRequireDefault(_base); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + /*istanbul ignore end*/var cssDiff = /*istanbul ignore start*/exports. /*istanbul ignore end*/cssDiff = new /*istanbul ignore start*/_base2['default'] /*istanbul ignore end*/(); + cssDiff.tokenize = function (value) { + return value.split(/([{}:;,]|\s+)/); + }; + + function diffCss(oldStr, newStr, callback) { + return cssDiff.diff(oldStr, newStr, callback); + } + + + +/***/ }), +/* 8 */ +/***/ (function(module, exports, __webpack_require__) { + + /*istanbul ignore start*/'use strict'; + + exports.__esModule = true; + exports.jsonDiff = undefined; + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + + exports. /*istanbul ignore end*/diffJson = diffJson; + /*istanbul ignore start*/exports. /*istanbul ignore end*/canonicalize = canonicalize; + + var /*istanbul ignore start*/_base = __webpack_require__(1) /*istanbul ignore end*/; + + /*istanbul ignore start*/var _base2 = _interopRequireDefault(_base); + + /*istanbul ignore end*/var /*istanbul ignore start*/_line = __webpack_require__(5) /*istanbul ignore end*/; + + /*istanbul ignore start*/function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + /*istanbul ignore end*/var objectPrototypeToString = Object.prototype.toString; + + var jsonDiff = /*istanbul ignore start*/exports. /*istanbul ignore end*/jsonDiff = new /*istanbul ignore start*/_base2['default'] /*istanbul ignore end*/(); + // Discriminate between two lines of pretty-printed, serialized JSON where one of them has a + // dangling comma and the other doesn't. Turns out including the dangling comma yields the nicest output: + jsonDiff.useLongestToken = true; + + jsonDiff.tokenize = /*istanbul ignore start*/_line.lineDiff /*istanbul ignore end*/.tokenize; + jsonDiff.castInput = function (value) { + /*istanbul ignore start*/var _options = /*istanbul ignore end*/this.options, + undefinedReplacement = _options.undefinedReplacement, + _options$stringifyRep = _options.stringifyReplacer, + stringifyReplacer = _options$stringifyRep === undefined ? function (k, v) /*istanbul ignore start*/{ + return (/*istanbul ignore end*/typeof v === 'undefined' ? undefinedReplacement : v + ); + } : _options$stringifyRep; + + + return typeof value === 'string' ? value : JSON.stringify(canonicalize(value, null, null, stringifyReplacer), stringifyReplacer, ' '); + }; + jsonDiff.equals = function (left, right) { + return (/*istanbul ignore start*/_base2['default'] /*istanbul ignore end*/.prototype.equals.call(jsonDiff, left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1')) + ); + }; + + function diffJson(oldObj, newObj, options) { + return jsonDiff.diff(oldObj, newObj, options); + } + + // This function handles the presence of circular references by bailing out when encountering an + // object that is already on the "stack" of items being processed. Accepts an optional replacer + function canonicalize(obj, stack, replacementStack, replacer, key) { + stack = stack || []; + replacementStack = replacementStack || []; + + if (replacer) { + obj = replacer(key, obj); + } + + var i = /*istanbul ignore start*/void 0 /*istanbul ignore end*/; + + for (i = 0; i < stack.length; i += 1) { + if (stack[i] === obj) { + return replacementStack[i]; + } + } + + var canonicalizedObj = /*istanbul ignore start*/void 0 /*istanbul ignore end*/; + + if ('[object Array]' === objectPrototypeToString.call(obj)) { + stack.push(obj); + canonicalizedObj = new Array(obj.length); + replacementStack.push(canonicalizedObj); + for (i = 0; i < obj.length; i += 1) { + canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack, replacer, key); + } + stack.pop(); + replacementStack.pop(); + return canonicalizedObj; + } + + if (obj && obj.toJSON) { + obj = obj.toJSON(); + } + + if ( /*istanbul ignore start*/(typeof /*istanbul ignore end*/obj === 'undefined' ? 'undefined' : _typeof(obj)) === 'object' && obj !== null) { + stack.push(obj); + canonicalizedObj = {}; + replacementStack.push(canonicalizedObj); + var sortedKeys = [], + _key = /*istanbul ignore start*/void 0 /*istanbul ignore end*/; + for (_key in obj) { + /* istanbul ignore else */ + if (obj.hasOwnProperty(_key)) { + sortedKeys.push(_key); + } + } + sortedKeys.sort(); + for (i = 0; i < sortedKeys.length; i += 1) { + _key = sortedKeys[i]; + canonicalizedObj[_key] = canonicalize(obj[_key], stack, replacementStack, replacer, _key); + } + stack.pop(); + replacementStack.pop(); + } else { + canonicalizedObj = obj; + } + return canonicalizedObj; + } + + + +/***/ }), +/* 9 */ +/***/ (function(module, exports, __webpack_require__) { + + /*istanbul ignore start*/'use strict'; + + exports.__esModule = true; + exports.arrayDiff = undefined; + exports. /*istanbul ignore end*/diffArrays = diffArrays; + + var /*istanbul ignore start*/_base = __webpack_require__(1) /*istanbul ignore end*/; + + /*istanbul ignore start*/var _base2 = _interopRequireDefault(_base); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + /*istanbul ignore end*/var arrayDiff = /*istanbul ignore start*/exports. /*istanbul ignore end*/arrayDiff = new /*istanbul ignore start*/_base2['default'] /*istanbul ignore end*/(); + arrayDiff.tokenize = function (value) { + return value.slice(); + }; + arrayDiff.join = arrayDiff.removeEmpty = function (value) { + return value; + }; + + function diffArrays(oldArr, newArr, callback) { + return arrayDiff.diff(oldArr, newArr, callback); + } + + + +/***/ }), +/* 10 */ +/***/ (function(module, exports, __webpack_require__) { + + /*istanbul ignore start*/'use strict'; + + exports.__esModule = true; + exports. /*istanbul ignore end*/applyPatch = applyPatch; + /*istanbul ignore start*/exports. /*istanbul ignore end*/applyPatches = applyPatches; + + var /*istanbul ignore start*/_parse = __webpack_require__(11) /*istanbul ignore end*/; + + var /*istanbul ignore start*/_distanceIterator = __webpack_require__(12) /*istanbul ignore end*/; + + /*istanbul ignore start*/var _distanceIterator2 = _interopRequireDefault(_distanceIterator); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + + /*istanbul ignore end*/function applyPatch(source, uniDiff) { + /*istanbul ignore start*/var /*istanbul ignore end*/options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + + if (typeof uniDiff === 'string') { + uniDiff = /*istanbul ignore start*/(0, _parse.parsePatch) /*istanbul ignore end*/(uniDiff); + } + + if (Array.isArray(uniDiff)) { + if (uniDiff.length > 1) { + throw new Error('applyPatch only works with a single input.'); + } + + uniDiff = uniDiff[0]; + } + + // Apply the diff to the input + var lines = source.split(/\r\n|[\n\v\f\r\x85]/), + delimiters = source.match(/\r\n|[\n\v\f\r\x85]/g) || [], + hunks = uniDiff.hunks, + compareLine = options.compareLine || function (lineNumber, line, operation, patchContent) /*istanbul ignore start*/{ + return (/*istanbul ignore end*/line === patchContent + ); + }, + errorCount = 0, + fuzzFactor = options.fuzzFactor || 0, + minLine = 0, + offset = 0, + removeEOFNL = /*istanbul ignore start*/void 0 /*istanbul ignore end*/, + addEOFNL = /*istanbul ignore start*/void 0 /*istanbul ignore end*/; + + /** + * Checks if the hunk exactly fits on the provided location + */ + function hunkFits(hunk, toPos) { + for (var j = 0; j < hunk.lines.length; j++) { + var line = hunk.lines[j], + operation = line.length > 0 ? line[0] : ' ', + content = line.length > 0 ? line.substr(1) : line; + + if (operation === ' ' || operation === '-') { + // Context sanity check + if (!compareLine(toPos + 1, lines[toPos], operation, content)) { + errorCount++; + + if (errorCount > fuzzFactor) { + return false; + } + } + toPos++; + } + } + + return true; + } + + // Search best fit offsets for each hunk based on the previous ones + for (var i = 0; i < hunks.length; i++) { + var hunk = hunks[i], + maxLine = lines.length - hunk.oldLines, + localOffset = 0, + toPos = offset + hunk.oldStart - 1; + + var iterator = /*istanbul ignore start*/(0, _distanceIterator2['default']) /*istanbul ignore end*/(toPos, minLine, maxLine); + + for (; localOffset !== undefined; localOffset = iterator()) { + if (hunkFits(hunk, toPos + localOffset)) { + hunk.offset = offset += localOffset; + break; + } + } + + if (localOffset === undefined) { + return false; + } + + // Set lower text limit to end of the current hunk, so next ones don't try + // to fit over already patched text + minLine = hunk.offset + hunk.oldStart + hunk.oldLines; + } + + // Apply patch hunks + var diffOffset = 0; + for (var _i = 0; _i < hunks.length; _i++) { + var _hunk = hunks[_i], + _toPos = _hunk.oldStart + _hunk.offset + diffOffset - 1; + diffOffset += _hunk.newLines - _hunk.oldLines; + + if (_toPos < 0) { + // Creating a new file + _toPos = 0; + } + + for (var j = 0; j < _hunk.lines.length; j++) { + var line = _hunk.lines[j], + operation = line.length > 0 ? line[0] : ' ', + content = line.length > 0 ? line.substr(1) : line, + delimiter = _hunk.linedelimiters[j]; + + if (operation === ' ') { + _toPos++; + } else if (operation === '-') { + lines.splice(_toPos, 1); + delimiters.splice(_toPos, 1); + /* istanbul ignore else */ + } else if (operation === '+') { + lines.splice(_toPos, 0, content); + delimiters.splice(_toPos, 0, delimiter); + _toPos++; + } else if (operation === '\\') { + var previousOperation = _hunk.lines[j - 1] ? _hunk.lines[j - 1][0] : null; + if (previousOperation === '+') { + removeEOFNL = true; + } else if (previousOperation === '-') { + addEOFNL = true; + } + } + } + } + + // Handle EOFNL insertion/removal + if (removeEOFNL) { + while (!lines[lines.length - 1]) { + lines.pop(); + delimiters.pop(); + } + } else if (addEOFNL) { + lines.push(''); + delimiters.push('\n'); + } + for (var _k = 0; _k < lines.length - 1; _k++) { + lines[_k] = lines[_k] + delimiters[_k]; + } + return lines.join(''); + } + + // Wrapper that supports multiple file patches via callbacks. + function applyPatches(uniDiff, options) { + if (typeof uniDiff === 'string') { + uniDiff = /*istanbul ignore start*/(0, _parse.parsePatch) /*istanbul ignore end*/(uniDiff); + } + + var currentIndex = 0; + function processIndex() { + var index = uniDiff[currentIndex++]; + if (!index) { + return options.complete(); + } + + options.loadFile(index, function (err, data) { + if (err) { + return options.complete(err); + } + + var updatedContent = applyPatch(data, index, options); + options.patched(index, updatedContent, function (err) { + if (err) { + return options.complete(err); + } + + processIndex(); + }); + }); + } + processIndex(); + } + + + +/***/ }), +/* 11 */ +/***/ (function(module, exports) { + + /*istanbul ignore start*/'use strict'; + + exports.__esModule = true; + exports. /*istanbul ignore end*/parsePatch = parsePatch; + function parsePatch(uniDiff) { + /*istanbul ignore start*/var /*istanbul ignore end*/options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + var diffstr = uniDiff.split(/\r\n|[\n\v\f\r\x85]/), + delimiters = uniDiff.match(/\r\n|[\n\v\f\r\x85]/g) || [], + list = [], + i = 0; + + function parseIndex() { + var index = {}; + list.push(index); + + // Parse diff metadata + while (i < diffstr.length) { + var line = diffstr[i]; + + // File header found, end parsing diff metadata + if (/^(\-\-\-|\+\+\+|@@)\s/.test(line)) { + break; + } + + // Diff index + var header = /^(?:Index:|diff(?: -r \w+)+)\s+(.+?)\s*$/.exec(line); + if (header) { + index.index = header[1]; + } + + i++; + } + + // Parse file headers if they are defined. Unified diff requires them, but + // there's no technical issues to have an isolated hunk without file header + parseFileHeader(index); + parseFileHeader(index); + + // Parse hunks + index.hunks = []; + + while (i < diffstr.length) { + var _line = diffstr[i]; + + if (/^(Index:|diff|\-\-\-|\+\+\+)\s/.test(_line)) { + break; + } else if (/^@@/.test(_line)) { + index.hunks.push(parseHunk()); + } else if (_line && options.strict) { + // Ignore unexpected content unless in strict mode + throw new Error('Unknown line ' + (i + 1) + ' ' + JSON.stringify(_line)); + } else { + i++; + } + } + } + + // Parses the --- and +++ headers, if none are found, no lines + // are consumed. + function parseFileHeader(index) { + var fileHeader = /^(---|\+\+\+)\s+(.*)$/.exec(diffstr[i]); + if (fileHeader) { + var keyPrefix = fileHeader[1] === '---' ? 'old' : 'new'; + var data = fileHeader[2].split('\t', 2); + var fileName = data[0].replace(/\\\\/g, '\\'); + if (/^".*"$/.test(fileName)) { + fileName = fileName.substr(1, fileName.length - 2); + } + index[keyPrefix + 'FileName'] = fileName; + index[keyPrefix + 'Header'] = (data[1] || '').trim(); + + i++; + } + } + + // Parses a hunk + // This assumes that we are at the start of a hunk. + function parseHunk() { + var chunkHeaderIndex = i, + chunkHeaderLine = diffstr[i++], + chunkHeader = chunkHeaderLine.split(/@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/); + + var hunk = { + oldStart: +chunkHeader[1], + oldLines: +chunkHeader[2] || 1, + newStart: +chunkHeader[3], + newLines: +chunkHeader[4] || 1, + lines: [], + linedelimiters: [] + }; + + var addCount = 0, + removeCount = 0; + for (; i < diffstr.length; i++) { + // Lines starting with '---' could be mistaken for the "remove line" operation + // But they could be the header for the next file. Therefore prune such cases out. + if (diffstr[i].indexOf('--- ') === 0 && i + 2 < diffstr.length && diffstr[i + 1].indexOf('+++ ') === 0 && diffstr[i + 2].indexOf('@@') === 0) { + break; + } + var operation = diffstr[i].length == 0 && i != diffstr.length - 1 ? ' ' : diffstr[i][0]; + + if (operation === '+' || operation === '-' || operation === ' ' || operation === '\\') { + hunk.lines.push(diffstr[i]); + hunk.linedelimiters.push(delimiters[i] || '\n'); + + if (operation === '+') { + addCount++; + } else if (operation === '-') { + removeCount++; + } else if (operation === ' ') { + addCount++; + removeCount++; + } + } else { + break; + } + } + + // Handle the empty block count case + if (!addCount && hunk.newLines === 1) { + hunk.newLines = 0; + } + if (!removeCount && hunk.oldLines === 1) { + hunk.oldLines = 0; + } + + // Perform optional sanity checking + if (options.strict) { + if (addCount !== hunk.newLines) { + throw new Error('Added line count did not match for hunk at line ' + (chunkHeaderIndex + 1)); + } + if (removeCount !== hunk.oldLines) { + throw new Error('Removed line count did not match for hunk at line ' + (chunkHeaderIndex + 1)); + } + } + + return hunk; + } + + while (i < diffstr.length) { + parseIndex(); + } + + return list; + } + + + +/***/ }), +/* 12 */ +/***/ (function(module, exports) { + + /*istanbul ignore start*/"use strict"; + + exports.__esModule = true; + + exports["default"] = /*istanbul ignore end*/function (start, minLine, maxLine) { + var wantForward = true, + backwardExhausted = false, + forwardExhausted = false, + localOffset = 1; + + return function iterator() { + if (wantForward && !forwardExhausted) { + if (backwardExhausted) { + localOffset++; + } else { + wantForward = false; + } + + // Check if trying to fit beyond text length, and if not, check it fits + // after offset location (or desired location on first iteration) + if (start + localOffset <= maxLine) { + return localOffset; + } + + forwardExhausted = true; + } + + if (!backwardExhausted) { + if (!forwardExhausted) { + wantForward = true; + } + + // Check if trying to fit before text beginning, and if not, check it fits + // before offset location + if (minLine <= start - localOffset) { + return -localOffset++; + } + + backwardExhausted = true; + return iterator(); + } + + // We tried to fit hunk before text beginning and beyond text length, then + // hunk can't fit on the text. Return undefined + }; + }; + + + +/***/ }), +/* 13 */ +/***/ (function(module, exports, __webpack_require__) { + + /*istanbul ignore start*/'use strict'; + + exports.__esModule = true; + exports. /*istanbul ignore end*/calcLineCount = calcLineCount; + /*istanbul ignore start*/exports. /*istanbul ignore end*/merge = merge; + + var /*istanbul ignore start*/_create = __webpack_require__(14) /*istanbul ignore end*/; + + var /*istanbul ignore start*/_parse = __webpack_require__(11) /*istanbul ignore end*/; + + var /*istanbul ignore start*/_array = __webpack_require__(15) /*istanbul ignore end*/; + + /*istanbul ignore start*/function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } + + /*istanbul ignore end*/function calcLineCount(hunk) { + /*istanbul ignore start*/var _calcOldNewLineCount = /*istanbul ignore end*/calcOldNewLineCount(hunk.lines), + oldLines = _calcOldNewLineCount.oldLines, + newLines = _calcOldNewLineCount.newLines; + + if (oldLines !== undefined) { + hunk.oldLines = oldLines; + } else { + delete hunk.oldLines; + } + + if (newLines !== undefined) { + hunk.newLines = newLines; + } else { + delete hunk.newLines; + } + } + + function merge(mine, theirs, base) { + mine = loadPatch(mine, base); + theirs = loadPatch(theirs, base); + + var ret = {}; + + // For index we just let it pass through as it doesn't have any necessary meaning. + // Leaving sanity checks on this to the API consumer that may know more about the + // meaning in their own context. + if (mine.index || theirs.index) { + ret.index = mine.index || theirs.index; + } + + if (mine.newFileName || theirs.newFileName) { + if (!fileNameChanged(mine)) { + // No header or no change in ours, use theirs (and ours if theirs does not exist) + ret.oldFileName = theirs.oldFileName || mine.oldFileName; + ret.newFileName = theirs.newFileName || mine.newFileName; + ret.oldHeader = theirs.oldHeader || mine.oldHeader; + ret.newHeader = theirs.newHeader || mine.newHeader; + } else if (!fileNameChanged(theirs)) { + // No header or no change in theirs, use ours + ret.oldFileName = mine.oldFileName; + ret.newFileName = mine.newFileName; + ret.oldHeader = mine.oldHeader; + ret.newHeader = mine.newHeader; + } else { + // Both changed... figure it out + ret.oldFileName = selectField(ret, mine.oldFileName, theirs.oldFileName); + ret.newFileName = selectField(ret, mine.newFileName, theirs.newFileName); + ret.oldHeader = selectField(ret, mine.oldHeader, theirs.oldHeader); + ret.newHeader = selectField(ret, mine.newHeader, theirs.newHeader); + } + } + + ret.hunks = []; + + var mineIndex = 0, + theirsIndex = 0, + mineOffset = 0, + theirsOffset = 0; + + while (mineIndex < mine.hunks.length || theirsIndex < theirs.hunks.length) { + var mineCurrent = mine.hunks[mineIndex] || { oldStart: Infinity }, + theirsCurrent = theirs.hunks[theirsIndex] || { oldStart: Infinity }; + + if (hunkBefore(mineCurrent, theirsCurrent)) { + // This patch does not overlap with any of the others, yay. + ret.hunks.push(cloneHunk(mineCurrent, mineOffset)); + mineIndex++; + theirsOffset += mineCurrent.newLines - mineCurrent.oldLines; + } else if (hunkBefore(theirsCurrent, mineCurrent)) { + // This patch does not overlap with any of the others, yay. + ret.hunks.push(cloneHunk(theirsCurrent, theirsOffset)); + theirsIndex++; + mineOffset += theirsCurrent.newLines - theirsCurrent.oldLines; + } else { + // Overlap, merge as best we can + var mergedHunk = { + oldStart: Math.min(mineCurrent.oldStart, theirsCurrent.oldStart), + oldLines: 0, + newStart: Math.min(mineCurrent.newStart + mineOffset, theirsCurrent.oldStart + theirsOffset), + newLines: 0, + lines: [] + }; + mergeLines(mergedHunk, mineCurrent.oldStart, mineCurrent.lines, theirsCurrent.oldStart, theirsCurrent.lines); + theirsIndex++; + mineIndex++; + + ret.hunks.push(mergedHunk); + } + } + + return ret; + } + + function loadPatch(param, base) { + if (typeof param === 'string') { + if (/^@@/m.test(param) || /^Index:/m.test(param)) { + return (/*istanbul ignore start*/(0, _parse.parsePatch) /*istanbul ignore end*/(param)[0] + ); + } + + if (!base) { + throw new Error('Must provide a base reference or pass in a patch'); + } + return (/*istanbul ignore start*/(0, _create.structuredPatch) /*istanbul ignore end*/(undefined, undefined, base, param) + ); + } + + return param; + } + + function fileNameChanged(patch) { + return patch.newFileName && patch.newFileName !== patch.oldFileName; + } + + function selectField(index, mine, theirs) { + if (mine === theirs) { + return mine; + } else { + index.conflict = true; + return { mine: mine, theirs: theirs }; + } + } + + function hunkBefore(test, check) { + return test.oldStart < check.oldStart && test.oldStart + test.oldLines < check.oldStart; + } + + function cloneHunk(hunk, offset) { + return { + oldStart: hunk.oldStart, oldLines: hunk.oldLines, + newStart: hunk.newStart + offset, newLines: hunk.newLines, + lines: hunk.lines + }; + } + + function mergeLines(hunk, mineOffset, mineLines, theirOffset, theirLines) { + // This will generally result in a conflicted hunk, but there are cases where the context + // is the only overlap where we can successfully merge the content here. + var mine = { offset: mineOffset, lines: mineLines, index: 0 }, + their = { offset: theirOffset, lines: theirLines, index: 0 }; + + // Handle any leading content + insertLeading(hunk, mine, their); + insertLeading(hunk, their, mine); + + // Now in the overlap content. Scan through and select the best changes from each. + while (mine.index < mine.lines.length && their.index < their.lines.length) { + var mineCurrent = mine.lines[mine.index], + theirCurrent = their.lines[their.index]; + + if ((mineCurrent[0] === '-' || mineCurrent[0] === '+') && (theirCurrent[0] === '-' || theirCurrent[0] === '+')) { + // Both modified ... + mutualChange(hunk, mine, their); + } else if (mineCurrent[0] === '+' && theirCurrent[0] === ' ') { + /*istanbul ignore start*/var _hunk$lines; + + /*istanbul ignore end*/ // Mine inserted + /*istanbul ignore start*/(_hunk$lines = /*istanbul ignore end*/hunk.lines).push. /*istanbul ignore start*/apply /*istanbul ignore end*/( /*istanbul ignore start*/_hunk$lines /*istanbul ignore end*/, /*istanbul ignore start*/_toConsumableArray( /*istanbul ignore end*/collectChange(mine))); + } else if (theirCurrent[0] === '+' && mineCurrent[0] === ' ') { + /*istanbul ignore start*/var _hunk$lines2; + + /*istanbul ignore end*/ // Theirs inserted + /*istanbul ignore start*/(_hunk$lines2 = /*istanbul ignore end*/hunk.lines).push. /*istanbul ignore start*/apply /*istanbul ignore end*/( /*istanbul ignore start*/_hunk$lines2 /*istanbul ignore end*/, /*istanbul ignore start*/_toConsumableArray( /*istanbul ignore end*/collectChange(their))); + } else if (mineCurrent[0] === '-' && theirCurrent[0] === ' ') { + // Mine removed or edited + removal(hunk, mine, their); + } else if (theirCurrent[0] === '-' && mineCurrent[0] === ' ') { + // Their removed or edited + removal(hunk, their, mine, true); + } else if (mineCurrent === theirCurrent) { + // Context identity + hunk.lines.push(mineCurrent); + mine.index++; + their.index++; + } else { + // Context mismatch + conflict(hunk, collectChange(mine), collectChange(their)); + } + } + + // Now push anything that may be remaining + insertTrailing(hunk, mine); + insertTrailing(hunk, their); + + calcLineCount(hunk); + } + + function mutualChange(hunk, mine, their) { + var myChanges = collectChange(mine), + theirChanges = collectChange(their); + + if (allRemoves(myChanges) && allRemoves(theirChanges)) { + // Special case for remove changes that are supersets of one another + if ( /*istanbul ignore start*/(0, _array.arrayStartsWith) /*istanbul ignore end*/(myChanges, theirChanges) && skipRemoveSuperset(their, myChanges, myChanges.length - theirChanges.length)) { + /*istanbul ignore start*/var _hunk$lines3; + + /*istanbul ignore end*/ /*istanbul ignore start*/(_hunk$lines3 = /*istanbul ignore end*/hunk.lines).push. /*istanbul ignore start*/apply /*istanbul ignore end*/( /*istanbul ignore start*/_hunk$lines3 /*istanbul ignore end*/, /*istanbul ignore start*/_toConsumableArray( /*istanbul ignore end*/myChanges)); + return; + } else if ( /*istanbul ignore start*/(0, _array.arrayStartsWith) /*istanbul ignore end*/(theirChanges, myChanges) && skipRemoveSuperset(mine, theirChanges, theirChanges.length - myChanges.length)) { + /*istanbul ignore start*/var _hunk$lines4; + + /*istanbul ignore end*/ /*istanbul ignore start*/(_hunk$lines4 = /*istanbul ignore end*/hunk.lines).push. /*istanbul ignore start*/apply /*istanbul ignore end*/( /*istanbul ignore start*/_hunk$lines4 /*istanbul ignore end*/, /*istanbul ignore start*/_toConsumableArray( /*istanbul ignore end*/theirChanges)); + return; + } + } else if ( /*istanbul ignore start*/(0, _array.arrayEqual) /*istanbul ignore end*/(myChanges, theirChanges)) { + /*istanbul ignore start*/var _hunk$lines5; + + /*istanbul ignore end*/ /*istanbul ignore start*/(_hunk$lines5 = /*istanbul ignore end*/hunk.lines).push. /*istanbul ignore start*/apply /*istanbul ignore end*/( /*istanbul ignore start*/_hunk$lines5 /*istanbul ignore end*/, /*istanbul ignore start*/_toConsumableArray( /*istanbul ignore end*/myChanges)); + return; + } + + conflict(hunk, myChanges, theirChanges); + } + + function removal(hunk, mine, their, swap) { + var myChanges = collectChange(mine), + theirChanges = collectContext(their, myChanges); + if (theirChanges.merged) { + /*istanbul ignore start*/var _hunk$lines6; + + /*istanbul ignore end*/ /*istanbul ignore start*/(_hunk$lines6 = /*istanbul ignore end*/hunk.lines).push. /*istanbul ignore start*/apply /*istanbul ignore end*/( /*istanbul ignore start*/_hunk$lines6 /*istanbul ignore end*/, /*istanbul ignore start*/_toConsumableArray( /*istanbul ignore end*/theirChanges.merged)); + } else { + conflict(hunk, swap ? theirChanges : myChanges, swap ? myChanges : theirChanges); + } + } + + function conflict(hunk, mine, their) { + hunk.conflict = true; + hunk.lines.push({ + conflict: true, + mine: mine, + theirs: their + }); + } + + function insertLeading(hunk, insert, their) { + while (insert.offset < their.offset && insert.index < insert.lines.length) { + var line = insert.lines[insert.index++]; + hunk.lines.push(line); + insert.offset++; + } + } + function insertTrailing(hunk, insert) { + while (insert.index < insert.lines.length) { + var line = insert.lines[insert.index++]; + hunk.lines.push(line); + } + } + + function collectChange(state) { + var ret = [], + operation = state.lines[state.index][0]; + while (state.index < state.lines.length) { + var line = state.lines[state.index]; + + // Group additions that are immediately after subtractions and treat them as one "atomic" modify change. + if (operation === '-' && line[0] === '+') { + operation = '+'; + } + + if (operation === line[0]) { + ret.push(line); + state.index++; + } else { + break; + } + } + + return ret; + } + function collectContext(state, matchChanges) { + var changes = [], + merged = [], + matchIndex = 0, + contextChanges = false, + conflicted = false; + while (matchIndex < matchChanges.length && state.index < state.lines.length) { + var change = state.lines[state.index], + match = matchChanges[matchIndex]; + + // Once we've hit our add, then we are done + if (match[0] === '+') { + break; + } + + contextChanges = contextChanges || change[0] !== ' '; + + merged.push(match); + matchIndex++; + + // Consume any additions in the other block as a conflict to attempt + // to pull in the remaining context after this + if (change[0] === '+') { + conflicted = true; + + while (change[0] === '+') { + changes.push(change); + change = state.lines[++state.index]; + } + } + + if (match.substr(1) === change.substr(1)) { + changes.push(change); + state.index++; + } else { + conflicted = true; + } + } + + if ((matchChanges[matchIndex] || '')[0] === '+' && contextChanges) { + conflicted = true; + } + + if (conflicted) { + return changes; + } + + while (matchIndex < matchChanges.length) { + merged.push(matchChanges[matchIndex++]); + } + + return { + merged: merged, + changes: changes + }; + } + + function allRemoves(changes) { + return changes.reduce(function (prev, change) { + return prev && change[0] === '-'; + }, true); + } + function skipRemoveSuperset(state, removeChanges, delta) { + for (var i = 0; i < delta; i++) { + var changeContent = removeChanges[removeChanges.length - delta + i].substr(1); + if (state.lines[state.index + i] !== ' ' + changeContent) { + return false; + } + } + + state.index += delta; + return true; + } + + function calcOldNewLineCount(lines) { + var oldLines = 0; + var newLines = 0; + + lines.forEach(function (line) { + if (typeof line !== 'string') { + var myCount = calcOldNewLineCount(line.mine); + var theirCount = calcOldNewLineCount(line.theirs); + + if (oldLines !== undefined) { + if (myCount.oldLines === theirCount.oldLines) { + oldLines += myCount.oldLines; + } else { + oldLines = undefined; + } + } + + if (newLines !== undefined) { + if (myCount.newLines === theirCount.newLines) { + newLines += myCount.newLines; + } else { + newLines = undefined; + } + } + } else { + if (newLines !== undefined && (line[0] === '+' || line[0] === ' ')) { + newLines++; + } + if (oldLines !== undefined && (line[0] === '-' || line[0] === ' ')) { + oldLines++; + } + } + }); + + return { oldLines: oldLines, newLines: newLines }; + } + + + +/***/ }), +/* 14 */ +/***/ (function(module, exports, __webpack_require__) { + + /*istanbul ignore start*/'use strict'; + + exports.__esModule = true; + exports. /*istanbul ignore end*/structuredPatch = structuredPatch; + /*istanbul ignore start*/exports. /*istanbul ignore end*/createTwoFilesPatch = createTwoFilesPatch; + /*istanbul ignore start*/exports. /*istanbul ignore end*/createPatch = createPatch; + + var /*istanbul ignore start*/_line = __webpack_require__(5) /*istanbul ignore end*/; + + /*istanbul ignore start*/function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } + + /*istanbul ignore end*/function structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) { + if (!options) { + options = {}; + } + if (typeof options.context === 'undefined') { + options.context = 4; + } + + var diff = /*istanbul ignore start*/(0, _line.diffLines) /*istanbul ignore end*/(oldStr, newStr, options); + diff.push({ value: '', lines: [] }); // Append an empty value to make cleanup easier + + function contextLines(lines) { + return lines.map(function (entry) { + return ' ' + entry; + }); + } + + var hunks = []; + var oldRangeStart = 0, + newRangeStart = 0, + curRange = [], + oldLine = 1, + newLine = 1; + + /*istanbul ignore start*/var _loop = function _loop( /*istanbul ignore end*/i) { + var current = diff[i], + lines = current.lines || current.value.replace(/\n$/, '').split('\n'); + current.lines = lines; + + if (current.added || current.removed) { + /*istanbul ignore start*/var _curRange; + + /*istanbul ignore end*/ // If we have previous context, start with that + if (!oldRangeStart) { + var prev = diff[i - 1]; + oldRangeStart = oldLine; + newRangeStart = newLine; + + if (prev) { + curRange = options.context > 0 ? contextLines(prev.lines.slice(-options.context)) : []; + oldRangeStart -= curRange.length; + newRangeStart -= curRange.length; + } + } + + // Output our changes + /*istanbul ignore start*/(_curRange = /*istanbul ignore end*/curRange).push. /*istanbul ignore start*/apply /*istanbul ignore end*/( /*istanbul ignore start*/_curRange /*istanbul ignore end*/, /*istanbul ignore start*/_toConsumableArray( /*istanbul ignore end*/lines.map(function (entry) { + return (current.added ? '+' : '-') + entry; + }))); + + // Track the updated file position + if (current.added) { + newLine += lines.length; + } else { + oldLine += lines.length; + } + } else { + // Identical context lines. Track line changes + if (oldRangeStart) { + // Close out any changes that have been output (or join overlapping) + if (lines.length <= options.context * 2 && i < diff.length - 2) { + /*istanbul ignore start*/var _curRange2; + + /*istanbul ignore end*/ // Overlapping + /*istanbul ignore start*/(_curRange2 = /*istanbul ignore end*/curRange).push. /*istanbul ignore start*/apply /*istanbul ignore end*/( /*istanbul ignore start*/_curRange2 /*istanbul ignore end*/, /*istanbul ignore start*/_toConsumableArray( /*istanbul ignore end*/contextLines(lines))); + } else { + /*istanbul ignore start*/var _curRange3; + + /*istanbul ignore end*/ // end the range and output + var contextSize = Math.min(lines.length, options.context); + /*istanbul ignore start*/(_curRange3 = /*istanbul ignore end*/curRange).push. /*istanbul ignore start*/apply /*istanbul ignore end*/( /*istanbul ignore start*/_curRange3 /*istanbul ignore end*/, /*istanbul ignore start*/_toConsumableArray( /*istanbul ignore end*/contextLines(lines.slice(0, contextSize)))); + + var hunk = { + oldStart: oldRangeStart, + oldLines: oldLine - oldRangeStart + contextSize, + newStart: newRangeStart, + newLines: newLine - newRangeStart + contextSize, + lines: curRange + }; + if (i >= diff.length - 2 && lines.length <= options.context) { + // EOF is inside this hunk + var oldEOFNewline = /\n$/.test(oldStr); + var newEOFNewline = /\n$/.test(newStr); + if (lines.length == 0 && !oldEOFNewline) { + // special case: old has no eol and no trailing context; no-nl can end up before adds + curRange.splice(hunk.oldLines, 0, '\\ No newline at end of file'); + } else if (!oldEOFNewline || !newEOFNewline) { + curRange.push('\\ No newline at end of file'); + } + } + hunks.push(hunk); + + oldRangeStart = 0; + newRangeStart = 0; + curRange = []; + } + } + oldLine += lines.length; + newLine += lines.length; + } + }; + + for (var i = 0; i < diff.length; i++) { + /*istanbul ignore start*/_loop( /*istanbul ignore end*/i); + } + + return { + oldFileName: oldFileName, newFileName: newFileName, + oldHeader: oldHeader, newHeader: newHeader, + hunks: hunks + }; + } + + function createTwoFilesPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) { + var diff = structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options); + + var ret = []; + if (oldFileName == newFileName) { + ret.push('Index: ' + oldFileName); + } + ret.push('==================================================================='); + ret.push('--- ' + diff.oldFileName + (typeof diff.oldHeader === 'undefined' ? '' : '\t' + diff.oldHeader)); + ret.push('+++ ' + diff.newFileName + (typeof diff.newHeader === 'undefined' ? '' : '\t' + diff.newHeader)); + + for (var i = 0; i < diff.hunks.length; i++) { + var hunk = diff.hunks[i]; + ret.push('@@ -' + hunk.oldStart + ',' + hunk.oldLines + ' +' + hunk.newStart + ',' + hunk.newLines + ' @@'); + ret.push.apply(ret, hunk.lines); + } + + return ret.join('\n') + '\n'; + } + + function createPatch(fileName, oldStr, newStr, oldHeader, newHeader, options) { + return createTwoFilesPatch(fileName, fileName, oldStr, newStr, oldHeader, newHeader, options); + } + + + +/***/ }), +/* 15 */ +/***/ (function(module, exports) { + + /*istanbul ignore start*/"use strict"; + + exports.__esModule = true; + exports. /*istanbul ignore end*/arrayEqual = arrayEqual; + /*istanbul ignore start*/exports. /*istanbul ignore end*/arrayStartsWith = arrayStartsWith; + function arrayEqual(a, b) { + if (a.length !== b.length) { + return false; + } + + return arrayStartsWith(a, b); + } + + function arrayStartsWith(array, start) { + if (start.length > array.length) { + return false; + } + + for (var i = 0; i < start.length; i++) { + if (start[i] !== array[i]) { + return false; + } + } + + return true; + } + + + +/***/ }), +/* 16 */ +/***/ (function(module, exports) { + + /*istanbul ignore start*/"use strict"; + + exports.__esModule = true; + exports. /*istanbul ignore end*/convertChangesToDMP = convertChangesToDMP; + // See: http://code.google.com/p/google-diff-match-patch/wiki/API + function convertChangesToDMP(changes) { + var ret = [], + change = /*istanbul ignore start*/void 0 /*istanbul ignore end*/, + operation = /*istanbul ignore start*/void 0 /*istanbul ignore end*/; + for (var i = 0; i < changes.length; i++) { + change = changes[i]; + if (change.added) { + operation = 1; + } else if (change.removed) { + operation = -1; + } else { + operation = 0; + } + + ret.push([operation, change.value]); + } + return ret; + } + + + +/***/ }), +/* 17 */ +/***/ (function(module, exports) { + + /*istanbul ignore start*/'use strict'; + + exports.__esModule = true; + exports. /*istanbul ignore end*/convertChangesToXML = convertChangesToXML; + function convertChangesToXML(changes) { + var ret = []; + for (var i = 0; i < changes.length; i++) { + var change = changes[i]; + if (change.added) { + ret.push(''); + } else if (change.removed) { + ret.push(''); + } + + ret.push(escapeHTML(change.value)); + + if (change.added) { + ret.push(''); + } else if (change.removed) { + ret.push(''); + } + } + return ret.join(''); + } + + function escapeHTML(s) { + var n = s; + n = n.replace(/&/g, '&'); + n = n.replace(//g, '>'); + n = n.replace(/"/g, '"'); + + return n; + } + + + +/***/ }) +/******/ ]) +}); +; +},{}],49:[function(require,module,exports){ +'use strict'; + +var matchOperatorsRe = /[|\\{}()[\]^$+*?.]/g; + +module.exports = function (str) { + if (typeof str !== 'string') { + throw new TypeError('Expected a string'); + } + + return str.replace(matchOperatorsRe, '\\$&'); +}; + +},{}],50:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var objectCreate = Object.create || objectCreatePolyfill +var objectKeys = Object.keys || objectKeysPolyfill +var bind = Function.prototype.bind || functionBindPolyfill + +function EventEmitter() { + if (!this._events || !Object.prototype.hasOwnProperty.call(this, '_events')) { + this._events = objectCreate(null); + this._eventsCount = 0; + } + + this._maxListeners = this._maxListeners || undefined; +} +module.exports = EventEmitter; + +// Backwards-compat with node 0.10.x +EventEmitter.EventEmitter = EventEmitter; + +EventEmitter.prototype._events = undefined; +EventEmitter.prototype._maxListeners = undefined; + +// By default EventEmitters will print a warning if more than 10 listeners are +// added to it. This is a useful default which helps finding memory leaks. +var defaultMaxListeners = 10; + +var hasDefineProperty; +try { + var o = {}; + if (Object.defineProperty) Object.defineProperty(o, 'x', { value: 0 }); + hasDefineProperty = o.x === 0; +} catch (err) { hasDefineProperty = false } +if (hasDefineProperty) { + Object.defineProperty(EventEmitter, 'defaultMaxListeners', { + enumerable: true, + get: function() { + return defaultMaxListeners; + }, + set: function(arg) { + // check whether the input is a positive number (whose value is zero or + // greater and not a NaN). + if (typeof arg !== 'number' || arg < 0 || arg !== arg) + throw new TypeError('"defaultMaxListeners" must be a positive number'); + defaultMaxListeners = arg; + } + }); +} else { + EventEmitter.defaultMaxListeners = defaultMaxListeners; +} + +// Obviously not all Emitters should be limited to 10. This function allows +// that to be increased. Set to zero for unlimited. +EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) { + if (typeof n !== 'number' || n < 0 || isNaN(n)) + throw new TypeError('"n" argument must be a positive number'); + this._maxListeners = n; + return this; +}; + +function $getMaxListeners(that) { + if (that._maxListeners === undefined) + return EventEmitter.defaultMaxListeners; + return that._maxListeners; +} + +EventEmitter.prototype.getMaxListeners = function getMaxListeners() { + return $getMaxListeners(this); +}; + +// These standalone emit* functions are used to optimize calling of event +// handlers for fast cases because emit() itself often has a variable number of +// arguments and can be deoptimized because of that. These functions always have +// the same number of arguments and thus do not get deoptimized, so the code +// inside them can execute faster. +function emitNone(handler, isFn, self) { + if (isFn) + handler.call(self); + else { + var len = handler.length; + var listeners = arrayClone(handler, len); + for (var i = 0; i < len; ++i) + listeners[i].call(self); + } +} +function emitOne(handler, isFn, self, arg1) { + if (isFn) + handler.call(self, arg1); + else { + var len = handler.length; + var listeners = arrayClone(handler, len); + for (var i = 0; i < len; ++i) + listeners[i].call(self, arg1); + } +} +function emitTwo(handler, isFn, self, arg1, arg2) { + if (isFn) + handler.call(self, arg1, arg2); + else { + var len = handler.length; + var listeners = arrayClone(handler, len); + for (var i = 0; i < len; ++i) + listeners[i].call(self, arg1, arg2); + } +} +function emitThree(handler, isFn, self, arg1, arg2, arg3) { + if (isFn) + handler.call(self, arg1, arg2, arg3); + else { + var len = handler.length; + var listeners = arrayClone(handler, len); + for (var i = 0; i < len; ++i) + listeners[i].call(self, arg1, arg2, arg3); + } +} + +function emitMany(handler, isFn, self, args) { + if (isFn) + handler.apply(self, args); + else { + var len = handler.length; + var listeners = arrayClone(handler, len); + for (var i = 0; i < len; ++i) + listeners[i].apply(self, args); + } +} + +EventEmitter.prototype.emit = function emit(type) { + var er, handler, len, args, i, events; + var doError = (type === 'error'); + + events = this._events; + if (events) + doError = (doError && events.error == null); + else if (!doError) + return false; + + // If there is no 'error' event listener then throw. + if (doError) { + if (arguments.length > 1) + er = arguments[1]; + if (er instanceof Error) { + throw er; // Unhandled 'error' event + } else { + // At least give some kind of context to the user + var err = new Error('Unhandled "error" event. (' + er + ')'); + err.context = er; + throw err; + } + return false; + } + + handler = events[type]; + + if (!handler) + return false; + + var isFn = typeof handler === 'function'; + len = arguments.length; + switch (len) { + // fast cases + case 1: + emitNone(handler, isFn, this); + break; + case 2: + emitOne(handler, isFn, this, arguments[1]); + break; + case 3: + emitTwo(handler, isFn, this, arguments[1], arguments[2]); + break; + case 4: + emitThree(handler, isFn, this, arguments[1], arguments[2], arguments[3]); + break; + // slower + default: + args = new Array(len - 1); + for (i = 1; i < len; i++) + args[i - 1] = arguments[i]; + emitMany(handler, isFn, this, args); + } + + return true; +}; + +function _addListener(target, type, listener, prepend) { + var m; + var events; + var existing; + + if (typeof listener !== 'function') + throw new TypeError('"listener" argument must be a function'); + + events = target._events; + if (!events) { + events = target._events = objectCreate(null); + target._eventsCount = 0; + } else { + // To avoid recursion in the case that type === "newListener"! Before + // adding it to the listeners, first emit "newListener". + if (events.newListener) { + target.emit('newListener', type, + listener.listener ? listener.listener : listener); + + // Re-assign `events` because a newListener handler could have caused the + // this._events to be assigned to a new object + events = target._events; + } + existing = events[type]; + } + + if (!existing) { + // Optimize the case of one listener. Don't need the extra array object. + existing = events[type] = listener; + ++target._eventsCount; + } else { + if (typeof existing === 'function') { + // Adding the second element, need to change to array. + existing = events[type] = + prepend ? [listener, existing] : [existing, listener]; + } else { + // If we've already got an array, just append. + if (prepend) { + existing.unshift(listener); + } else { + existing.push(listener); + } + } + + // Check for listener leak + if (!existing.warned) { + m = $getMaxListeners(target); + if (m && m > 0 && existing.length > m) { + existing.warned = true; + var w = new Error('Possible EventEmitter memory leak detected. ' + + existing.length + ' "' + String(type) + '" listeners ' + + 'added. Use emitter.setMaxListeners() to ' + + 'increase limit.'); + w.name = 'MaxListenersExceededWarning'; + w.emitter = target; + w.type = type; + w.count = existing.length; + if (typeof console === 'object' && console.warn) { + console.warn('%s: %s', w.name, w.message); + } + } + } + } + + return target; +} + +EventEmitter.prototype.addListener = function addListener(type, listener) { + return _addListener(this, type, listener, false); +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +EventEmitter.prototype.prependListener = + function prependListener(type, listener) { + return _addListener(this, type, listener, true); + }; + +function onceWrapper() { + if (!this.fired) { + this.target.removeListener(this.type, this.wrapFn); + this.fired = true; + switch (arguments.length) { + case 0: + return this.listener.call(this.target); + case 1: + return this.listener.call(this.target, arguments[0]); + case 2: + return this.listener.call(this.target, arguments[0], arguments[1]); + case 3: + return this.listener.call(this.target, arguments[0], arguments[1], + arguments[2]); + default: + var args = new Array(arguments.length); + for (var i = 0; i < args.length; ++i) + args[i] = arguments[i]; + this.listener.apply(this.target, args); + } + } +} + +function _onceWrap(target, type, listener) { + var state = { fired: false, wrapFn: undefined, target: target, type: type, listener: listener }; + var wrapped = bind.call(onceWrapper, state); + wrapped.listener = listener; + state.wrapFn = wrapped; + return wrapped; +} + +EventEmitter.prototype.once = function once(type, listener) { + if (typeof listener !== 'function') + throw new TypeError('"listener" argument must be a function'); + this.on(type, _onceWrap(this, type, listener)); + return this; +}; + +EventEmitter.prototype.prependOnceListener = + function prependOnceListener(type, listener) { + if (typeof listener !== 'function') + throw new TypeError('"listener" argument must be a function'); + this.prependListener(type, _onceWrap(this, type, listener)); + return this; + }; + +// Emits a 'removeListener' event if and only if the listener was removed. +EventEmitter.prototype.removeListener = + function removeListener(type, listener) { + var list, events, position, i, originalListener; + + if (typeof listener !== 'function') + throw new TypeError('"listener" argument must be a function'); + + events = this._events; + if (!events) + return this; + + list = events[type]; + if (!list) + return this; + + if (list === listener || list.listener === listener) { + if (--this._eventsCount === 0) + this._events = objectCreate(null); + else { + delete events[type]; + if (events.removeListener) + this.emit('removeListener', type, list.listener || listener); + } + } else if (typeof list !== 'function') { + position = -1; + + for (i = list.length - 1; i >= 0; i--) { + if (list[i] === listener || list[i].listener === listener) { + originalListener = list[i].listener; + position = i; + break; + } + } + + if (position < 0) + return this; + + if (position === 0) + list.shift(); + else + spliceOne(list, position); + + if (list.length === 1) + events[type] = list[0]; + + if (events.removeListener) + this.emit('removeListener', type, originalListener || listener); + } + + return this; + }; + +EventEmitter.prototype.removeAllListeners = + function removeAllListeners(type) { + var listeners, events, i; + + events = this._events; + if (!events) + return this; + + // not listening for removeListener, no need to emit + if (!events.removeListener) { + if (arguments.length === 0) { + this._events = objectCreate(null); + this._eventsCount = 0; + } else if (events[type]) { + if (--this._eventsCount === 0) + this._events = objectCreate(null); + else + delete events[type]; + } + return this; + } + + // emit removeListener for all listeners on all events + if (arguments.length === 0) { + var keys = objectKeys(events); + var key; + for (i = 0; i < keys.length; ++i) { + key = keys[i]; + if (key === 'removeListener') continue; + this.removeAllListeners(key); + } + this.removeAllListeners('removeListener'); + this._events = objectCreate(null); + this._eventsCount = 0; + return this; + } + + listeners = events[type]; + + if (typeof listeners === 'function') { + this.removeListener(type, listeners); + } else if (listeners) { + // LIFO order + for (i = listeners.length - 1; i >= 0; i--) { + this.removeListener(type, listeners[i]); + } + } + + return this; + }; + +function _listeners(target, type, unwrap) { + var events = target._events; + + if (!events) + return []; + + var evlistener = events[type]; + if (!evlistener) + return []; + + if (typeof evlistener === 'function') + return unwrap ? [evlistener.listener || evlistener] : [evlistener]; + + return unwrap ? unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length); +} + +EventEmitter.prototype.listeners = function listeners(type) { + return _listeners(this, type, true); +}; + +EventEmitter.prototype.rawListeners = function rawListeners(type) { + return _listeners(this, type, false); +}; + +EventEmitter.listenerCount = function(emitter, type) { + if (typeof emitter.listenerCount === 'function') { + return emitter.listenerCount(type); + } else { + return listenerCount.call(emitter, type); + } +}; + +EventEmitter.prototype.listenerCount = listenerCount; +function listenerCount(type) { + var events = this._events; + + if (events) { + var evlistener = events[type]; + + if (typeof evlistener === 'function') { + return 1; + } else if (evlistener) { + return evlistener.length; + } + } + + return 0; +} + +EventEmitter.prototype.eventNames = function eventNames() { + return this._eventsCount > 0 ? Reflect.ownKeys(this._events) : []; +}; + +// About 1.5x faster than the two-arg version of Array#splice(). +function spliceOne(list, index) { + for (var i = index, k = i + 1, n = list.length; k < n; i += 1, k += 1) + list[i] = list[k]; + list.pop(); +} + +function arrayClone(arr, n) { + var copy = new Array(n); + for (var i = 0; i < n; ++i) + copy[i] = arr[i]; + return copy; +} + +function unwrapListeners(arr) { + var ret = new Array(arr.length); + for (var i = 0; i < ret.length; ++i) { + ret[i] = arr[i].listener || arr[i]; + } + return ret; +} + +function objectCreatePolyfill(proto) { + var F = function() {}; + F.prototype = proto; + return new F; +} +function objectKeysPolyfill(obj) { + var keys = []; + for (var k in obj) if (Object.prototype.hasOwnProperty.call(obj, k)) { + keys.push(k); + } + return k; +} +function functionBindPolyfill(context) { + var fn = this; + return function () { + return fn.apply(context, arguments); }; +} - /** - * Setup mocha with the given setting options. - */ +},{}],51:[function(require,module,exports){ +'use strict'; - mocha.setup = function(opts){ - if ('string' == typeof opts) opts = { ui: opts }; - for (var opt in opts) this[opt](opts[opt]); - return this; - }; +/* eslint no-invalid-this: 1 */ - /** - * Run mocha, returning the Runner. - */ +var ERROR_MESSAGE = 'Function.prototype.bind called on incompatible '; +var slice = Array.prototype.slice; +var toStr = Object.prototype.toString; +var funcType = '[object Function]'; - mocha.run = function(fn){ - var options = mocha.options; - mocha.globals('location'); +module.exports = function bind(that) { + var target = this; + if (typeof target !== 'function' || toStr.call(target) !== funcType) { + throw new TypeError(ERROR_MESSAGE + target); + } + var args = slice.call(arguments, 1); - //var query = Mocha.utils.parseQuery(window.location.search || ''); - //if (query.grep) mocha.grep(query.grep); + var bound; + var binder = function () { + if (this instanceof bound) { + var result = target.apply( + this, + args.concat(slice.call(arguments)) + ); + if (Object(result) === result) { + return result; + } + return this; + } else { + return target.apply( + that, + args.concat(slice.call(arguments)) + ); + } + }; - return Mocha.prototype.run.call(mocha, function(){ - Mocha.utils.highlightTags('code'); - if (fn) fn(); + var boundLength = Math.max(0, target.length - args.length); + var boundArgs = []; + for (var i = 0; i < boundLength; i++) { + boundArgs.push('$' + i); + } + + bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this,arguments); }')(binder); + + if (target.prototype) { + var Empty = function Empty() {}; + Empty.prototype = target.prototype; + bound.prototype = new Empty(); + Empty.prototype = null; + } + + return bound; +}; + +},{}],52:[function(require,module,exports){ +'use strict'; + +var implementation = require('./implementation'); + +module.exports = Function.prototype.bind || implementation; + +},{"./implementation":51}],53:[function(require,module,exports){ +'use strict'; + +/* eslint complexity: [2, 17], max-statements: [2, 33] */ +module.exports = function hasSymbols() { + if (typeof Symbol !== 'function' || typeof Object.getOwnPropertySymbols !== 'function') { return false; } + if (typeof Symbol.iterator === 'symbol') { return true; } + + var obj = {}; + var sym = Symbol('test'); + var symObj = Object(sym); + if (typeof sym === 'string') { return false; } + + if (Object.prototype.toString.call(sym) !== '[object Symbol]') { return false; } + if (Object.prototype.toString.call(symObj) !== '[object Symbol]') { return false; } + + // temp disabled per https://github.com/ljharb/object.assign/issues/17 + // if (sym instanceof Symbol) { return false; } + // temp disabled per https://github.com/WebReflection/get-own-property-symbols/issues/4 + // if (!(symObj instanceof Symbol)) { return false; } + + // if (typeof Symbol.prototype.toString !== 'function') { return false; } + // if (String(sym) !== Symbol.prototype.toString.call(sym)) { return false; } + + var symVal = 42; + obj[sym] = symVal; + for (sym in obj) { return false; } // eslint-disable-line no-restricted-syntax + if (typeof Object.keys === 'function' && Object.keys(obj).length !== 0) { return false; } + + if (typeof Object.getOwnPropertyNames === 'function' && Object.getOwnPropertyNames(obj).length !== 0) { return false; } + + var syms = Object.getOwnPropertySymbols(obj); + if (syms.length !== 1 || syms[0] !== sym) { return false; } + + if (!Object.prototype.propertyIsEnumerable.call(obj, sym)) { return false; } + + if (typeof Object.getOwnPropertyDescriptor === 'function') { + var descriptor = Object.getOwnPropertyDescriptor(obj, sym); + if (descriptor.value !== symVal || descriptor.enumerable !== true) { return false; } + } + + return true; +}; + +},{}],54:[function(require,module,exports){ +(function (global){ +/*! https://mths.be/he v1.2.0 by @mathias | MIT license */ +;(function(root) { + + // Detect free variables `exports`. + var freeExports = typeof exports == 'object' && exports; + + // Detect free variable `module`. + var freeModule = typeof module == 'object' && module && + module.exports == freeExports && module; + + // Detect free variable `global`, from Node.js or Browserified code, + // and use it as `root`. + var freeGlobal = typeof global == 'object' && global; + if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) { + root = freeGlobal; + } + + /*--------------------------------------------------------------------------*/ + + // All astral symbols. + var regexAstralSymbols = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g; + // All ASCII symbols (not just printable ASCII) except those listed in the + // first column of the overrides table. + // https://html.spec.whatwg.org/multipage/syntax.html#table-charref-overrides + var regexAsciiWhitelist = /[\x01-\x7F]/g; + // All BMP symbols that are not ASCII newlines, printable ASCII symbols, or + // code points listed in the first column of the overrides table on + // https://html.spec.whatwg.org/multipage/syntax.html#table-charref-overrides. + var regexBmpWhitelist = /[\x01-\t\x0B\f\x0E-\x1F\x7F\x81\x8D\x8F\x90\x9D\xA0-\uFFFF]/g; + + var regexEncodeNonAscii = /<\u20D2|=\u20E5|>\u20D2|\u205F\u200A|\u219D\u0338|\u2202\u0338|\u2220\u20D2|\u2229\uFE00|\u222A\uFE00|\u223C\u20D2|\u223D\u0331|\u223E\u0333|\u2242\u0338|\u224B\u0338|\u224D\u20D2|\u224E\u0338|\u224F\u0338|\u2250\u0338|\u2261\u20E5|\u2264\u20D2|\u2265\u20D2|\u2266\u0338|\u2267\u0338|\u2268\uFE00|\u2269\uFE00|\u226A\u0338|\u226A\u20D2|\u226B\u0338|\u226B\u20D2|\u227F\u0338|\u2282\u20D2|\u2283\u20D2|\u228A\uFE00|\u228B\uFE00|\u228F\u0338|\u2290\u0338|\u2293\uFE00|\u2294\uFE00|\u22B4\u20D2|\u22B5\u20D2|\u22D8\u0338|\u22D9\u0338|\u22DA\uFE00|\u22DB\uFE00|\u22F5\u0338|\u22F9\u0338|\u2933\u0338|\u29CF\u0338|\u29D0\u0338|\u2A6D\u0338|\u2A70\u0338|\u2A7D\u0338|\u2A7E\u0338|\u2AA1\u0338|\u2AA2\u0338|\u2AAC\uFE00|\u2AAD\uFE00|\u2AAF\u0338|\u2AB0\u0338|\u2AC5\u0338|\u2AC6\u0338|\u2ACB\uFE00|\u2ACC\uFE00|\u2AFD\u20E5|[\xA0-\u0113\u0116-\u0122\u0124-\u012B\u012E-\u014D\u0150-\u017E\u0192\u01B5\u01F5\u0237\u02C6\u02C7\u02D8-\u02DD\u0311\u0391-\u03A1\u03A3-\u03A9\u03B1-\u03C9\u03D1\u03D2\u03D5\u03D6\u03DC\u03DD\u03F0\u03F1\u03F5\u03F6\u0401-\u040C\u040E-\u044F\u0451-\u045C\u045E\u045F\u2002-\u2005\u2007-\u2010\u2013-\u2016\u2018-\u201A\u201C-\u201E\u2020-\u2022\u2025\u2026\u2030-\u2035\u2039\u203A\u203E\u2041\u2043\u2044\u204F\u2057\u205F-\u2063\u20AC\u20DB\u20DC\u2102\u2105\u210A-\u2113\u2115-\u211E\u2122\u2124\u2127-\u2129\u212C\u212D\u212F-\u2131\u2133-\u2138\u2145-\u2148\u2153-\u215E\u2190-\u219B\u219D-\u21A7\u21A9-\u21AE\u21B0-\u21B3\u21B5-\u21B7\u21BA-\u21DB\u21DD\u21E4\u21E5\u21F5\u21FD-\u2205\u2207-\u2209\u220B\u220C\u220F-\u2214\u2216-\u2218\u221A\u221D-\u2238\u223A-\u2257\u2259\u225A\u225C\u225F-\u2262\u2264-\u228B\u228D-\u229B\u229D-\u22A5\u22A7-\u22B0\u22B2-\u22BB\u22BD-\u22DB\u22DE-\u22E3\u22E6-\u22F7\u22F9-\u22FE\u2305\u2306\u2308-\u2310\u2312\u2313\u2315\u2316\u231C-\u231F\u2322\u2323\u232D\u232E\u2336\u233D\u233F\u237C\u23B0\u23B1\u23B4-\u23B6\u23DC-\u23DF\u23E2\u23E7\u2423\u24C8\u2500\u2502\u250C\u2510\u2514\u2518\u251C\u2524\u252C\u2534\u253C\u2550-\u256C\u2580\u2584\u2588\u2591-\u2593\u25A1\u25AA\u25AB\u25AD\u25AE\u25B1\u25B3-\u25B5\u25B8\u25B9\u25BD-\u25BF\u25C2\u25C3\u25CA\u25CB\u25EC\u25EF\u25F8-\u25FC\u2605\u2606\u260E\u2640\u2642\u2660\u2663\u2665\u2666\u266A\u266D-\u266F\u2713\u2717\u2720\u2736\u2758\u2772\u2773\u27C8\u27C9\u27E6-\u27ED\u27F5-\u27FA\u27FC\u27FF\u2902-\u2905\u290C-\u2913\u2916\u2919-\u2920\u2923-\u292A\u2933\u2935-\u2939\u293C\u293D\u2945\u2948-\u294B\u294E-\u2976\u2978\u2979\u297B-\u297F\u2985\u2986\u298B-\u2996\u299A\u299C\u299D\u29A4-\u29B7\u29B9\u29BB\u29BC\u29BE-\u29C5\u29C9\u29CD-\u29D0\u29DC-\u29DE\u29E3-\u29E5\u29EB\u29F4\u29F6\u2A00-\u2A02\u2A04\u2A06\u2A0C\u2A0D\u2A10-\u2A17\u2A22-\u2A27\u2A29\u2A2A\u2A2D-\u2A31\u2A33-\u2A3C\u2A3F\u2A40\u2A42-\u2A4D\u2A50\u2A53-\u2A58\u2A5A-\u2A5D\u2A5F\u2A66\u2A6A\u2A6D-\u2A75\u2A77-\u2A9A\u2A9D-\u2AA2\u2AA4-\u2AB0\u2AB3-\u2AC8\u2ACB\u2ACC\u2ACF-\u2ADB\u2AE4\u2AE6-\u2AE9\u2AEB-\u2AF3\u2AFD\uFB00-\uFB04]|\uD835[\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDCCF\uDD04\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDD6B]/g; + var encodeMap = {'\xAD':'shy','\u200C':'zwnj','\u200D':'zwj','\u200E':'lrm','\u2063':'ic','\u2062':'it','\u2061':'af','\u200F':'rlm','\u200B':'ZeroWidthSpace','\u2060':'NoBreak','\u0311':'DownBreve','\u20DB':'tdot','\u20DC':'DotDot','\t':'Tab','\n':'NewLine','\u2008':'puncsp','\u205F':'MediumSpace','\u2009':'thinsp','\u200A':'hairsp','\u2004':'emsp13','\u2002':'ensp','\u2005':'emsp14','\u2003':'emsp','\u2007':'numsp','\xA0':'nbsp','\u205F\u200A':'ThickSpace','\u203E':'oline','_':'lowbar','\u2010':'dash','\u2013':'ndash','\u2014':'mdash','\u2015':'horbar',',':'comma',';':'semi','\u204F':'bsemi',':':'colon','\u2A74':'Colone','!':'excl','\xA1':'iexcl','?':'quest','\xBF':'iquest','.':'period','\u2025':'nldr','\u2026':'mldr','\xB7':'middot','\'':'apos','\u2018':'lsquo','\u2019':'rsquo','\u201A':'sbquo','\u2039':'lsaquo','\u203A':'rsaquo','"':'quot','\u201C':'ldquo','\u201D':'rdquo','\u201E':'bdquo','\xAB':'laquo','\xBB':'raquo','(':'lpar',')':'rpar','[':'lsqb',']':'rsqb','{':'lcub','}':'rcub','\u2308':'lceil','\u2309':'rceil','\u230A':'lfloor','\u230B':'rfloor','\u2985':'lopar','\u2986':'ropar','\u298B':'lbrke','\u298C':'rbrke','\u298D':'lbrkslu','\u298E':'rbrksld','\u298F':'lbrksld','\u2990':'rbrkslu','\u2991':'langd','\u2992':'rangd','\u2993':'lparlt','\u2994':'rpargt','\u2995':'gtlPar','\u2996':'ltrPar','\u27E6':'lobrk','\u27E7':'robrk','\u27E8':'lang','\u27E9':'rang','\u27EA':'Lang','\u27EB':'Rang','\u27EC':'loang','\u27ED':'roang','\u2772':'lbbrk','\u2773':'rbbrk','\u2016':'Vert','\xA7':'sect','\xB6':'para','@':'commat','*':'ast','/':'sol','undefined':null,'&':'amp','#':'num','%':'percnt','\u2030':'permil','\u2031':'pertenk','\u2020':'dagger','\u2021':'Dagger','\u2022':'bull','\u2043':'hybull','\u2032':'prime','\u2033':'Prime','\u2034':'tprime','\u2057':'qprime','\u2035':'bprime','\u2041':'caret','`':'grave','\xB4':'acute','\u02DC':'tilde','^':'Hat','\xAF':'macr','\u02D8':'breve','\u02D9':'dot','\xA8':'die','\u02DA':'ring','\u02DD':'dblac','\xB8':'cedil','\u02DB':'ogon','\u02C6':'circ','\u02C7':'caron','\xB0':'deg','\xA9':'copy','\xAE':'reg','\u2117':'copysr','\u2118':'wp','\u211E':'rx','\u2127':'mho','\u2129':'iiota','\u2190':'larr','\u219A':'nlarr','\u2192':'rarr','\u219B':'nrarr','\u2191':'uarr','\u2193':'darr','\u2194':'harr','\u21AE':'nharr','\u2195':'varr','\u2196':'nwarr','\u2197':'nearr','\u2198':'searr','\u2199':'swarr','\u219D':'rarrw','\u219D\u0338':'nrarrw','\u219E':'Larr','\u219F':'Uarr','\u21A0':'Rarr','\u21A1':'Darr','\u21A2':'larrtl','\u21A3':'rarrtl','\u21A4':'mapstoleft','\u21A5':'mapstoup','\u21A6':'map','\u21A7':'mapstodown','\u21A9':'larrhk','\u21AA':'rarrhk','\u21AB':'larrlp','\u21AC':'rarrlp','\u21AD':'harrw','\u21B0':'lsh','\u21B1':'rsh','\u21B2':'ldsh','\u21B3':'rdsh','\u21B5':'crarr','\u21B6':'cularr','\u21B7':'curarr','\u21BA':'olarr','\u21BB':'orarr','\u21BC':'lharu','\u21BD':'lhard','\u21BE':'uharr','\u21BF':'uharl','\u21C0':'rharu','\u21C1':'rhard','\u21C2':'dharr','\u21C3':'dharl','\u21C4':'rlarr','\u21C5':'udarr','\u21C6':'lrarr','\u21C7':'llarr','\u21C8':'uuarr','\u21C9':'rrarr','\u21CA':'ddarr','\u21CB':'lrhar','\u21CC':'rlhar','\u21D0':'lArr','\u21CD':'nlArr','\u21D1':'uArr','\u21D2':'rArr','\u21CF':'nrArr','\u21D3':'dArr','\u21D4':'iff','\u21CE':'nhArr','\u21D5':'vArr','\u21D6':'nwArr','\u21D7':'neArr','\u21D8':'seArr','\u21D9':'swArr','\u21DA':'lAarr','\u21DB':'rAarr','\u21DD':'zigrarr','\u21E4':'larrb','\u21E5':'rarrb','\u21F5':'duarr','\u21FD':'loarr','\u21FE':'roarr','\u21FF':'hoarr','\u2200':'forall','\u2201':'comp','\u2202':'part','\u2202\u0338':'npart','\u2203':'exist','\u2204':'nexist','\u2205':'empty','\u2207':'Del','\u2208':'in','\u2209':'notin','\u220B':'ni','\u220C':'notni','\u03F6':'bepsi','\u220F':'prod','\u2210':'coprod','\u2211':'sum','+':'plus','\xB1':'pm','\xF7':'div','\xD7':'times','<':'lt','\u226E':'nlt','<\u20D2':'nvlt','=':'equals','\u2260':'ne','=\u20E5':'bne','\u2A75':'Equal','>':'gt','\u226F':'ngt','>\u20D2':'nvgt','\xAC':'not','|':'vert','\xA6':'brvbar','\u2212':'minus','\u2213':'mp','\u2214':'plusdo','\u2044':'frasl','\u2216':'setmn','\u2217':'lowast','\u2218':'compfn','\u221A':'Sqrt','\u221D':'prop','\u221E':'infin','\u221F':'angrt','\u2220':'ang','\u2220\u20D2':'nang','\u2221':'angmsd','\u2222':'angsph','\u2223':'mid','\u2224':'nmid','\u2225':'par','\u2226':'npar','\u2227':'and','\u2228':'or','\u2229':'cap','\u2229\uFE00':'caps','\u222A':'cup','\u222A\uFE00':'cups','\u222B':'int','\u222C':'Int','\u222D':'tint','\u2A0C':'qint','\u222E':'oint','\u222F':'Conint','\u2230':'Cconint','\u2231':'cwint','\u2232':'cwconint','\u2233':'awconint','\u2234':'there4','\u2235':'becaus','\u2236':'ratio','\u2237':'Colon','\u2238':'minusd','\u223A':'mDDot','\u223B':'homtht','\u223C':'sim','\u2241':'nsim','\u223C\u20D2':'nvsim','\u223D':'bsim','\u223D\u0331':'race','\u223E':'ac','\u223E\u0333':'acE','\u223F':'acd','\u2240':'wr','\u2242':'esim','\u2242\u0338':'nesim','\u2243':'sime','\u2244':'nsime','\u2245':'cong','\u2247':'ncong','\u2246':'simne','\u2248':'ap','\u2249':'nap','\u224A':'ape','\u224B':'apid','\u224B\u0338':'napid','\u224C':'bcong','\u224D':'CupCap','\u226D':'NotCupCap','\u224D\u20D2':'nvap','\u224E':'bump','\u224E\u0338':'nbump','\u224F':'bumpe','\u224F\u0338':'nbumpe','\u2250':'doteq','\u2250\u0338':'nedot','\u2251':'eDot','\u2252':'efDot','\u2253':'erDot','\u2254':'colone','\u2255':'ecolon','\u2256':'ecir','\u2257':'cire','\u2259':'wedgeq','\u225A':'veeeq','\u225C':'trie','\u225F':'equest','\u2261':'equiv','\u2262':'nequiv','\u2261\u20E5':'bnequiv','\u2264':'le','\u2270':'nle','\u2264\u20D2':'nvle','\u2265':'ge','\u2271':'nge','\u2265\u20D2':'nvge','\u2266':'lE','\u2266\u0338':'nlE','\u2267':'gE','\u2267\u0338':'ngE','\u2268\uFE00':'lvnE','\u2268':'lnE','\u2269':'gnE','\u2269\uFE00':'gvnE','\u226A':'ll','\u226A\u0338':'nLtv','\u226A\u20D2':'nLt','\u226B':'gg','\u226B\u0338':'nGtv','\u226B\u20D2':'nGt','\u226C':'twixt','\u2272':'lsim','\u2274':'nlsim','\u2273':'gsim','\u2275':'ngsim','\u2276':'lg','\u2278':'ntlg','\u2277':'gl','\u2279':'ntgl','\u227A':'pr','\u2280':'npr','\u227B':'sc','\u2281':'nsc','\u227C':'prcue','\u22E0':'nprcue','\u227D':'sccue','\u22E1':'nsccue','\u227E':'prsim','\u227F':'scsim','\u227F\u0338':'NotSucceedsTilde','\u2282':'sub','\u2284':'nsub','\u2282\u20D2':'vnsub','\u2283':'sup','\u2285':'nsup','\u2283\u20D2':'vnsup','\u2286':'sube','\u2288':'nsube','\u2287':'supe','\u2289':'nsupe','\u228A\uFE00':'vsubne','\u228A':'subne','\u228B\uFE00':'vsupne','\u228B':'supne','\u228D':'cupdot','\u228E':'uplus','\u228F':'sqsub','\u228F\u0338':'NotSquareSubset','\u2290':'sqsup','\u2290\u0338':'NotSquareSuperset','\u2291':'sqsube','\u22E2':'nsqsube','\u2292':'sqsupe','\u22E3':'nsqsupe','\u2293':'sqcap','\u2293\uFE00':'sqcaps','\u2294':'sqcup','\u2294\uFE00':'sqcups','\u2295':'oplus','\u2296':'ominus','\u2297':'otimes','\u2298':'osol','\u2299':'odot','\u229A':'ocir','\u229B':'oast','\u229D':'odash','\u229E':'plusb','\u229F':'minusb','\u22A0':'timesb','\u22A1':'sdotb','\u22A2':'vdash','\u22AC':'nvdash','\u22A3':'dashv','\u22A4':'top','\u22A5':'bot','\u22A7':'models','\u22A8':'vDash','\u22AD':'nvDash','\u22A9':'Vdash','\u22AE':'nVdash','\u22AA':'Vvdash','\u22AB':'VDash','\u22AF':'nVDash','\u22B0':'prurel','\u22B2':'vltri','\u22EA':'nltri','\u22B3':'vrtri','\u22EB':'nrtri','\u22B4':'ltrie','\u22EC':'nltrie','\u22B4\u20D2':'nvltrie','\u22B5':'rtrie','\u22ED':'nrtrie','\u22B5\u20D2':'nvrtrie','\u22B6':'origof','\u22B7':'imof','\u22B8':'mumap','\u22B9':'hercon','\u22BA':'intcal','\u22BB':'veebar','\u22BD':'barvee','\u22BE':'angrtvb','\u22BF':'lrtri','\u22C0':'Wedge','\u22C1':'Vee','\u22C2':'xcap','\u22C3':'xcup','\u22C4':'diam','\u22C5':'sdot','\u22C6':'Star','\u22C7':'divonx','\u22C8':'bowtie','\u22C9':'ltimes','\u22CA':'rtimes','\u22CB':'lthree','\u22CC':'rthree','\u22CD':'bsime','\u22CE':'cuvee','\u22CF':'cuwed','\u22D0':'Sub','\u22D1':'Sup','\u22D2':'Cap','\u22D3':'Cup','\u22D4':'fork','\u22D5':'epar','\u22D6':'ltdot','\u22D7':'gtdot','\u22D8':'Ll','\u22D8\u0338':'nLl','\u22D9':'Gg','\u22D9\u0338':'nGg','\u22DA\uFE00':'lesg','\u22DA':'leg','\u22DB':'gel','\u22DB\uFE00':'gesl','\u22DE':'cuepr','\u22DF':'cuesc','\u22E6':'lnsim','\u22E7':'gnsim','\u22E8':'prnsim','\u22E9':'scnsim','\u22EE':'vellip','\u22EF':'ctdot','\u22F0':'utdot','\u22F1':'dtdot','\u22F2':'disin','\u22F3':'isinsv','\u22F4':'isins','\u22F5':'isindot','\u22F5\u0338':'notindot','\u22F6':'notinvc','\u22F7':'notinvb','\u22F9':'isinE','\u22F9\u0338':'notinE','\u22FA':'nisd','\u22FB':'xnis','\u22FC':'nis','\u22FD':'notnivc','\u22FE':'notnivb','\u2305':'barwed','\u2306':'Barwed','\u230C':'drcrop','\u230D':'dlcrop','\u230E':'urcrop','\u230F':'ulcrop','\u2310':'bnot','\u2312':'profline','\u2313':'profsurf','\u2315':'telrec','\u2316':'target','\u231C':'ulcorn','\u231D':'urcorn','\u231E':'dlcorn','\u231F':'drcorn','\u2322':'frown','\u2323':'smile','\u232D':'cylcty','\u232E':'profalar','\u2336':'topbot','\u233D':'ovbar','\u233F':'solbar','\u237C':'angzarr','\u23B0':'lmoust','\u23B1':'rmoust','\u23B4':'tbrk','\u23B5':'bbrk','\u23B6':'bbrktbrk','\u23DC':'OverParenthesis','\u23DD':'UnderParenthesis','\u23DE':'OverBrace','\u23DF':'UnderBrace','\u23E2':'trpezium','\u23E7':'elinters','\u2423':'blank','\u2500':'boxh','\u2502':'boxv','\u250C':'boxdr','\u2510':'boxdl','\u2514':'boxur','\u2518':'boxul','\u251C':'boxvr','\u2524':'boxvl','\u252C':'boxhd','\u2534':'boxhu','\u253C':'boxvh','\u2550':'boxH','\u2551':'boxV','\u2552':'boxdR','\u2553':'boxDr','\u2554':'boxDR','\u2555':'boxdL','\u2556':'boxDl','\u2557':'boxDL','\u2558':'boxuR','\u2559':'boxUr','\u255A':'boxUR','\u255B':'boxuL','\u255C':'boxUl','\u255D':'boxUL','\u255E':'boxvR','\u255F':'boxVr','\u2560':'boxVR','\u2561':'boxvL','\u2562':'boxVl','\u2563':'boxVL','\u2564':'boxHd','\u2565':'boxhD','\u2566':'boxHD','\u2567':'boxHu','\u2568':'boxhU','\u2569':'boxHU','\u256A':'boxvH','\u256B':'boxVh','\u256C':'boxVH','\u2580':'uhblk','\u2584':'lhblk','\u2588':'block','\u2591':'blk14','\u2592':'blk12','\u2593':'blk34','\u25A1':'squ','\u25AA':'squf','\u25AB':'EmptyVerySmallSquare','\u25AD':'rect','\u25AE':'marker','\u25B1':'fltns','\u25B3':'xutri','\u25B4':'utrif','\u25B5':'utri','\u25B8':'rtrif','\u25B9':'rtri','\u25BD':'xdtri','\u25BE':'dtrif','\u25BF':'dtri','\u25C2':'ltrif','\u25C3':'ltri','\u25CA':'loz','\u25CB':'cir','\u25EC':'tridot','\u25EF':'xcirc','\u25F8':'ultri','\u25F9':'urtri','\u25FA':'lltri','\u25FB':'EmptySmallSquare','\u25FC':'FilledSmallSquare','\u2605':'starf','\u2606':'star','\u260E':'phone','\u2640':'female','\u2642':'male','\u2660':'spades','\u2663':'clubs','\u2665':'hearts','\u2666':'diams','\u266A':'sung','\u2713':'check','\u2717':'cross','\u2720':'malt','\u2736':'sext','\u2758':'VerticalSeparator','\u27C8':'bsolhsub','\u27C9':'suphsol','\u27F5':'xlarr','\u27F6':'xrarr','\u27F7':'xharr','\u27F8':'xlArr','\u27F9':'xrArr','\u27FA':'xhArr','\u27FC':'xmap','\u27FF':'dzigrarr','\u2902':'nvlArr','\u2903':'nvrArr','\u2904':'nvHarr','\u2905':'Map','\u290C':'lbarr','\u290D':'rbarr','\u290E':'lBarr','\u290F':'rBarr','\u2910':'RBarr','\u2911':'DDotrahd','\u2912':'UpArrowBar','\u2913':'DownArrowBar','\u2916':'Rarrtl','\u2919':'latail','\u291A':'ratail','\u291B':'lAtail','\u291C':'rAtail','\u291D':'larrfs','\u291E':'rarrfs','\u291F':'larrbfs','\u2920':'rarrbfs','\u2923':'nwarhk','\u2924':'nearhk','\u2925':'searhk','\u2926':'swarhk','\u2927':'nwnear','\u2928':'toea','\u2929':'tosa','\u292A':'swnwar','\u2933':'rarrc','\u2933\u0338':'nrarrc','\u2935':'cudarrr','\u2936':'ldca','\u2937':'rdca','\u2938':'cudarrl','\u2939':'larrpl','\u293C':'curarrm','\u293D':'cularrp','\u2945':'rarrpl','\u2948':'harrcir','\u2949':'Uarrocir','\u294A':'lurdshar','\u294B':'ldrushar','\u294E':'LeftRightVector','\u294F':'RightUpDownVector','\u2950':'DownLeftRightVector','\u2951':'LeftUpDownVector','\u2952':'LeftVectorBar','\u2953':'RightVectorBar','\u2954':'RightUpVectorBar','\u2955':'RightDownVectorBar','\u2956':'DownLeftVectorBar','\u2957':'DownRightVectorBar','\u2958':'LeftUpVectorBar','\u2959':'LeftDownVectorBar','\u295A':'LeftTeeVector','\u295B':'RightTeeVector','\u295C':'RightUpTeeVector','\u295D':'RightDownTeeVector','\u295E':'DownLeftTeeVector','\u295F':'DownRightTeeVector','\u2960':'LeftUpTeeVector','\u2961':'LeftDownTeeVector','\u2962':'lHar','\u2963':'uHar','\u2964':'rHar','\u2965':'dHar','\u2966':'luruhar','\u2967':'ldrdhar','\u2968':'ruluhar','\u2969':'rdldhar','\u296A':'lharul','\u296B':'llhard','\u296C':'rharul','\u296D':'lrhard','\u296E':'udhar','\u296F':'duhar','\u2970':'RoundImplies','\u2971':'erarr','\u2972':'simrarr','\u2973':'larrsim','\u2974':'rarrsim','\u2975':'rarrap','\u2976':'ltlarr','\u2978':'gtrarr','\u2979':'subrarr','\u297B':'suplarr','\u297C':'lfisht','\u297D':'rfisht','\u297E':'ufisht','\u297F':'dfisht','\u299A':'vzigzag','\u299C':'vangrt','\u299D':'angrtvbd','\u29A4':'ange','\u29A5':'range','\u29A6':'dwangle','\u29A7':'uwangle','\u29A8':'angmsdaa','\u29A9':'angmsdab','\u29AA':'angmsdac','\u29AB':'angmsdad','\u29AC':'angmsdae','\u29AD':'angmsdaf','\u29AE':'angmsdag','\u29AF':'angmsdah','\u29B0':'bemptyv','\u29B1':'demptyv','\u29B2':'cemptyv','\u29B3':'raemptyv','\u29B4':'laemptyv','\u29B5':'ohbar','\u29B6':'omid','\u29B7':'opar','\u29B9':'operp','\u29BB':'olcross','\u29BC':'odsold','\u29BE':'olcir','\u29BF':'ofcir','\u29C0':'olt','\u29C1':'ogt','\u29C2':'cirscir','\u29C3':'cirE','\u29C4':'solb','\u29C5':'bsolb','\u29C9':'boxbox','\u29CD':'trisb','\u29CE':'rtriltri','\u29CF':'LeftTriangleBar','\u29CF\u0338':'NotLeftTriangleBar','\u29D0':'RightTriangleBar','\u29D0\u0338':'NotRightTriangleBar','\u29DC':'iinfin','\u29DD':'infintie','\u29DE':'nvinfin','\u29E3':'eparsl','\u29E4':'smeparsl','\u29E5':'eqvparsl','\u29EB':'lozf','\u29F4':'RuleDelayed','\u29F6':'dsol','\u2A00':'xodot','\u2A01':'xoplus','\u2A02':'xotime','\u2A04':'xuplus','\u2A06':'xsqcup','\u2A0D':'fpartint','\u2A10':'cirfnint','\u2A11':'awint','\u2A12':'rppolint','\u2A13':'scpolint','\u2A14':'npolint','\u2A15':'pointint','\u2A16':'quatint','\u2A17':'intlarhk','\u2A22':'pluscir','\u2A23':'plusacir','\u2A24':'simplus','\u2A25':'plusdu','\u2A26':'plussim','\u2A27':'plustwo','\u2A29':'mcomma','\u2A2A':'minusdu','\u2A2D':'loplus','\u2A2E':'roplus','\u2A2F':'Cross','\u2A30':'timesd','\u2A31':'timesbar','\u2A33':'smashp','\u2A34':'lotimes','\u2A35':'rotimes','\u2A36':'otimesas','\u2A37':'Otimes','\u2A38':'odiv','\u2A39':'triplus','\u2A3A':'triminus','\u2A3B':'tritime','\u2A3C':'iprod','\u2A3F':'amalg','\u2A40':'capdot','\u2A42':'ncup','\u2A43':'ncap','\u2A44':'capand','\u2A45':'cupor','\u2A46':'cupcap','\u2A47':'capcup','\u2A48':'cupbrcap','\u2A49':'capbrcup','\u2A4A':'cupcup','\u2A4B':'capcap','\u2A4C':'ccups','\u2A4D':'ccaps','\u2A50':'ccupssm','\u2A53':'And','\u2A54':'Or','\u2A55':'andand','\u2A56':'oror','\u2A57':'orslope','\u2A58':'andslope','\u2A5A':'andv','\u2A5B':'orv','\u2A5C':'andd','\u2A5D':'ord','\u2A5F':'wedbar','\u2A66':'sdote','\u2A6A':'simdot','\u2A6D':'congdot','\u2A6D\u0338':'ncongdot','\u2A6E':'easter','\u2A6F':'apacir','\u2A70':'apE','\u2A70\u0338':'napE','\u2A71':'eplus','\u2A72':'pluse','\u2A73':'Esim','\u2A77':'eDDot','\u2A78':'equivDD','\u2A79':'ltcir','\u2A7A':'gtcir','\u2A7B':'ltquest','\u2A7C':'gtquest','\u2A7D':'les','\u2A7D\u0338':'nles','\u2A7E':'ges','\u2A7E\u0338':'nges','\u2A7F':'lesdot','\u2A80':'gesdot','\u2A81':'lesdoto','\u2A82':'gesdoto','\u2A83':'lesdotor','\u2A84':'gesdotol','\u2A85':'lap','\u2A86':'gap','\u2A87':'lne','\u2A88':'gne','\u2A89':'lnap','\u2A8A':'gnap','\u2A8B':'lEg','\u2A8C':'gEl','\u2A8D':'lsime','\u2A8E':'gsime','\u2A8F':'lsimg','\u2A90':'gsiml','\u2A91':'lgE','\u2A92':'glE','\u2A93':'lesges','\u2A94':'gesles','\u2A95':'els','\u2A96':'egs','\u2A97':'elsdot','\u2A98':'egsdot','\u2A99':'el','\u2A9A':'eg','\u2A9D':'siml','\u2A9E':'simg','\u2A9F':'simlE','\u2AA0':'simgE','\u2AA1':'LessLess','\u2AA1\u0338':'NotNestedLessLess','\u2AA2':'GreaterGreater','\u2AA2\u0338':'NotNestedGreaterGreater','\u2AA4':'glj','\u2AA5':'gla','\u2AA6':'ltcc','\u2AA7':'gtcc','\u2AA8':'lescc','\u2AA9':'gescc','\u2AAA':'smt','\u2AAB':'lat','\u2AAC':'smte','\u2AAC\uFE00':'smtes','\u2AAD':'late','\u2AAD\uFE00':'lates','\u2AAE':'bumpE','\u2AAF':'pre','\u2AAF\u0338':'npre','\u2AB0':'sce','\u2AB0\u0338':'nsce','\u2AB3':'prE','\u2AB4':'scE','\u2AB5':'prnE','\u2AB6':'scnE','\u2AB7':'prap','\u2AB8':'scap','\u2AB9':'prnap','\u2ABA':'scnap','\u2ABB':'Pr','\u2ABC':'Sc','\u2ABD':'subdot','\u2ABE':'supdot','\u2ABF':'subplus','\u2AC0':'supplus','\u2AC1':'submult','\u2AC2':'supmult','\u2AC3':'subedot','\u2AC4':'supedot','\u2AC5':'subE','\u2AC5\u0338':'nsubE','\u2AC6':'supE','\u2AC6\u0338':'nsupE','\u2AC7':'subsim','\u2AC8':'supsim','\u2ACB\uFE00':'vsubnE','\u2ACB':'subnE','\u2ACC\uFE00':'vsupnE','\u2ACC':'supnE','\u2ACF':'csub','\u2AD0':'csup','\u2AD1':'csube','\u2AD2':'csupe','\u2AD3':'subsup','\u2AD4':'supsub','\u2AD5':'subsub','\u2AD6':'supsup','\u2AD7':'suphsub','\u2AD8':'supdsub','\u2AD9':'forkv','\u2ADA':'topfork','\u2ADB':'mlcp','\u2AE4':'Dashv','\u2AE6':'Vdashl','\u2AE7':'Barv','\u2AE8':'vBar','\u2AE9':'vBarv','\u2AEB':'Vbar','\u2AEC':'Not','\u2AED':'bNot','\u2AEE':'rnmid','\u2AEF':'cirmid','\u2AF0':'midcir','\u2AF1':'topcir','\u2AF2':'nhpar','\u2AF3':'parsim','\u2AFD':'parsl','\u2AFD\u20E5':'nparsl','\u266D':'flat','\u266E':'natur','\u266F':'sharp','\xA4':'curren','\xA2':'cent','$':'dollar','\xA3':'pound','\xA5':'yen','\u20AC':'euro','\xB9':'sup1','\xBD':'half','\u2153':'frac13','\xBC':'frac14','\u2155':'frac15','\u2159':'frac16','\u215B':'frac18','\xB2':'sup2','\u2154':'frac23','\u2156':'frac25','\xB3':'sup3','\xBE':'frac34','\u2157':'frac35','\u215C':'frac38','\u2158':'frac45','\u215A':'frac56','\u215D':'frac58','\u215E':'frac78','\uD835\uDCB6':'ascr','\uD835\uDD52':'aopf','\uD835\uDD1E':'afr','\uD835\uDD38':'Aopf','\uD835\uDD04':'Afr','\uD835\uDC9C':'Ascr','\xAA':'ordf','\xE1':'aacute','\xC1':'Aacute','\xE0':'agrave','\xC0':'Agrave','\u0103':'abreve','\u0102':'Abreve','\xE2':'acirc','\xC2':'Acirc','\xE5':'aring','\xC5':'angst','\xE4':'auml','\xC4':'Auml','\xE3':'atilde','\xC3':'Atilde','\u0105':'aogon','\u0104':'Aogon','\u0101':'amacr','\u0100':'Amacr','\xE6':'aelig','\xC6':'AElig','\uD835\uDCB7':'bscr','\uD835\uDD53':'bopf','\uD835\uDD1F':'bfr','\uD835\uDD39':'Bopf','\u212C':'Bscr','\uD835\uDD05':'Bfr','\uD835\uDD20':'cfr','\uD835\uDCB8':'cscr','\uD835\uDD54':'copf','\u212D':'Cfr','\uD835\uDC9E':'Cscr','\u2102':'Copf','\u0107':'cacute','\u0106':'Cacute','\u0109':'ccirc','\u0108':'Ccirc','\u010D':'ccaron','\u010C':'Ccaron','\u010B':'cdot','\u010A':'Cdot','\xE7':'ccedil','\xC7':'Ccedil','\u2105':'incare','\uD835\uDD21':'dfr','\u2146':'dd','\uD835\uDD55':'dopf','\uD835\uDCB9':'dscr','\uD835\uDC9F':'Dscr','\uD835\uDD07':'Dfr','\u2145':'DD','\uD835\uDD3B':'Dopf','\u010F':'dcaron','\u010E':'Dcaron','\u0111':'dstrok','\u0110':'Dstrok','\xF0':'eth','\xD0':'ETH','\u2147':'ee','\u212F':'escr','\uD835\uDD22':'efr','\uD835\uDD56':'eopf','\u2130':'Escr','\uD835\uDD08':'Efr','\uD835\uDD3C':'Eopf','\xE9':'eacute','\xC9':'Eacute','\xE8':'egrave','\xC8':'Egrave','\xEA':'ecirc','\xCA':'Ecirc','\u011B':'ecaron','\u011A':'Ecaron','\xEB':'euml','\xCB':'Euml','\u0117':'edot','\u0116':'Edot','\u0119':'eogon','\u0118':'Eogon','\u0113':'emacr','\u0112':'Emacr','\uD835\uDD23':'ffr','\uD835\uDD57':'fopf','\uD835\uDCBB':'fscr','\uD835\uDD09':'Ffr','\uD835\uDD3D':'Fopf','\u2131':'Fscr','\uFB00':'fflig','\uFB03':'ffilig','\uFB04':'ffllig','\uFB01':'filig','fj':'fjlig','\uFB02':'fllig','\u0192':'fnof','\u210A':'gscr','\uD835\uDD58':'gopf','\uD835\uDD24':'gfr','\uD835\uDCA2':'Gscr','\uD835\uDD3E':'Gopf','\uD835\uDD0A':'Gfr','\u01F5':'gacute','\u011F':'gbreve','\u011E':'Gbreve','\u011D':'gcirc','\u011C':'Gcirc','\u0121':'gdot','\u0120':'Gdot','\u0122':'Gcedil','\uD835\uDD25':'hfr','\u210E':'planckh','\uD835\uDCBD':'hscr','\uD835\uDD59':'hopf','\u210B':'Hscr','\u210C':'Hfr','\u210D':'Hopf','\u0125':'hcirc','\u0124':'Hcirc','\u210F':'hbar','\u0127':'hstrok','\u0126':'Hstrok','\uD835\uDD5A':'iopf','\uD835\uDD26':'ifr','\uD835\uDCBE':'iscr','\u2148':'ii','\uD835\uDD40':'Iopf','\u2110':'Iscr','\u2111':'Im','\xED':'iacute','\xCD':'Iacute','\xEC':'igrave','\xCC':'Igrave','\xEE':'icirc','\xCE':'Icirc','\xEF':'iuml','\xCF':'Iuml','\u0129':'itilde','\u0128':'Itilde','\u0130':'Idot','\u012F':'iogon','\u012E':'Iogon','\u012B':'imacr','\u012A':'Imacr','\u0133':'ijlig','\u0132':'IJlig','\u0131':'imath','\uD835\uDCBF':'jscr','\uD835\uDD5B':'jopf','\uD835\uDD27':'jfr','\uD835\uDCA5':'Jscr','\uD835\uDD0D':'Jfr','\uD835\uDD41':'Jopf','\u0135':'jcirc','\u0134':'Jcirc','\u0237':'jmath','\uD835\uDD5C':'kopf','\uD835\uDCC0':'kscr','\uD835\uDD28':'kfr','\uD835\uDCA6':'Kscr','\uD835\uDD42':'Kopf','\uD835\uDD0E':'Kfr','\u0137':'kcedil','\u0136':'Kcedil','\uD835\uDD29':'lfr','\uD835\uDCC1':'lscr','\u2113':'ell','\uD835\uDD5D':'lopf','\u2112':'Lscr','\uD835\uDD0F':'Lfr','\uD835\uDD43':'Lopf','\u013A':'lacute','\u0139':'Lacute','\u013E':'lcaron','\u013D':'Lcaron','\u013C':'lcedil','\u013B':'Lcedil','\u0142':'lstrok','\u0141':'Lstrok','\u0140':'lmidot','\u013F':'Lmidot','\uD835\uDD2A':'mfr','\uD835\uDD5E':'mopf','\uD835\uDCC2':'mscr','\uD835\uDD10':'Mfr','\uD835\uDD44':'Mopf','\u2133':'Mscr','\uD835\uDD2B':'nfr','\uD835\uDD5F':'nopf','\uD835\uDCC3':'nscr','\u2115':'Nopf','\uD835\uDCA9':'Nscr','\uD835\uDD11':'Nfr','\u0144':'nacute','\u0143':'Nacute','\u0148':'ncaron','\u0147':'Ncaron','\xF1':'ntilde','\xD1':'Ntilde','\u0146':'ncedil','\u0145':'Ncedil','\u2116':'numero','\u014B':'eng','\u014A':'ENG','\uD835\uDD60':'oopf','\uD835\uDD2C':'ofr','\u2134':'oscr','\uD835\uDCAA':'Oscr','\uD835\uDD12':'Ofr','\uD835\uDD46':'Oopf','\xBA':'ordm','\xF3':'oacute','\xD3':'Oacute','\xF2':'ograve','\xD2':'Ograve','\xF4':'ocirc','\xD4':'Ocirc','\xF6':'ouml','\xD6':'Ouml','\u0151':'odblac','\u0150':'Odblac','\xF5':'otilde','\xD5':'Otilde','\xF8':'oslash','\xD8':'Oslash','\u014D':'omacr','\u014C':'Omacr','\u0153':'oelig','\u0152':'OElig','\uD835\uDD2D':'pfr','\uD835\uDCC5':'pscr','\uD835\uDD61':'popf','\u2119':'Popf','\uD835\uDD13':'Pfr','\uD835\uDCAB':'Pscr','\uD835\uDD62':'qopf','\uD835\uDD2E':'qfr','\uD835\uDCC6':'qscr','\uD835\uDCAC':'Qscr','\uD835\uDD14':'Qfr','\u211A':'Qopf','\u0138':'kgreen','\uD835\uDD2F':'rfr','\uD835\uDD63':'ropf','\uD835\uDCC7':'rscr','\u211B':'Rscr','\u211C':'Re','\u211D':'Ropf','\u0155':'racute','\u0154':'Racute','\u0159':'rcaron','\u0158':'Rcaron','\u0157':'rcedil','\u0156':'Rcedil','\uD835\uDD64':'sopf','\uD835\uDCC8':'sscr','\uD835\uDD30':'sfr','\uD835\uDD4A':'Sopf','\uD835\uDD16':'Sfr','\uD835\uDCAE':'Sscr','\u24C8':'oS','\u015B':'sacute','\u015A':'Sacute','\u015D':'scirc','\u015C':'Scirc','\u0161':'scaron','\u0160':'Scaron','\u015F':'scedil','\u015E':'Scedil','\xDF':'szlig','\uD835\uDD31':'tfr','\uD835\uDCC9':'tscr','\uD835\uDD65':'topf','\uD835\uDCAF':'Tscr','\uD835\uDD17':'Tfr','\uD835\uDD4B':'Topf','\u0165':'tcaron','\u0164':'Tcaron','\u0163':'tcedil','\u0162':'Tcedil','\u2122':'trade','\u0167':'tstrok','\u0166':'Tstrok','\uD835\uDCCA':'uscr','\uD835\uDD66':'uopf','\uD835\uDD32':'ufr','\uD835\uDD4C':'Uopf','\uD835\uDD18':'Ufr','\uD835\uDCB0':'Uscr','\xFA':'uacute','\xDA':'Uacute','\xF9':'ugrave','\xD9':'Ugrave','\u016D':'ubreve','\u016C':'Ubreve','\xFB':'ucirc','\xDB':'Ucirc','\u016F':'uring','\u016E':'Uring','\xFC':'uuml','\xDC':'Uuml','\u0171':'udblac','\u0170':'Udblac','\u0169':'utilde','\u0168':'Utilde','\u0173':'uogon','\u0172':'Uogon','\u016B':'umacr','\u016A':'Umacr','\uD835\uDD33':'vfr','\uD835\uDD67':'vopf','\uD835\uDCCB':'vscr','\uD835\uDD19':'Vfr','\uD835\uDD4D':'Vopf','\uD835\uDCB1':'Vscr','\uD835\uDD68':'wopf','\uD835\uDCCC':'wscr','\uD835\uDD34':'wfr','\uD835\uDCB2':'Wscr','\uD835\uDD4E':'Wopf','\uD835\uDD1A':'Wfr','\u0175':'wcirc','\u0174':'Wcirc','\uD835\uDD35':'xfr','\uD835\uDCCD':'xscr','\uD835\uDD69':'xopf','\uD835\uDD4F':'Xopf','\uD835\uDD1B':'Xfr','\uD835\uDCB3':'Xscr','\uD835\uDD36':'yfr','\uD835\uDCCE':'yscr','\uD835\uDD6A':'yopf','\uD835\uDCB4':'Yscr','\uD835\uDD1C':'Yfr','\uD835\uDD50':'Yopf','\xFD':'yacute','\xDD':'Yacute','\u0177':'ycirc','\u0176':'Ycirc','\xFF':'yuml','\u0178':'Yuml','\uD835\uDCCF':'zscr','\uD835\uDD37':'zfr','\uD835\uDD6B':'zopf','\u2128':'Zfr','\u2124':'Zopf','\uD835\uDCB5':'Zscr','\u017A':'zacute','\u0179':'Zacute','\u017E':'zcaron','\u017D':'Zcaron','\u017C':'zdot','\u017B':'Zdot','\u01B5':'imped','\xFE':'thorn','\xDE':'THORN','\u0149':'napos','\u03B1':'alpha','\u0391':'Alpha','\u03B2':'beta','\u0392':'Beta','\u03B3':'gamma','\u0393':'Gamma','\u03B4':'delta','\u0394':'Delta','\u03B5':'epsi','\u03F5':'epsiv','\u0395':'Epsilon','\u03DD':'gammad','\u03DC':'Gammad','\u03B6':'zeta','\u0396':'Zeta','\u03B7':'eta','\u0397':'Eta','\u03B8':'theta','\u03D1':'thetav','\u0398':'Theta','\u03B9':'iota','\u0399':'Iota','\u03BA':'kappa','\u03F0':'kappav','\u039A':'Kappa','\u03BB':'lambda','\u039B':'Lambda','\u03BC':'mu','\xB5':'micro','\u039C':'Mu','\u03BD':'nu','\u039D':'Nu','\u03BE':'xi','\u039E':'Xi','\u03BF':'omicron','\u039F':'Omicron','\u03C0':'pi','\u03D6':'piv','\u03A0':'Pi','\u03C1':'rho','\u03F1':'rhov','\u03A1':'Rho','\u03C3':'sigma','\u03A3':'Sigma','\u03C2':'sigmaf','\u03C4':'tau','\u03A4':'Tau','\u03C5':'upsi','\u03A5':'Upsilon','\u03D2':'Upsi','\u03C6':'phi','\u03D5':'phiv','\u03A6':'Phi','\u03C7':'chi','\u03A7':'Chi','\u03C8':'psi','\u03A8':'Psi','\u03C9':'omega','\u03A9':'ohm','\u0430':'acy','\u0410':'Acy','\u0431':'bcy','\u0411':'Bcy','\u0432':'vcy','\u0412':'Vcy','\u0433':'gcy','\u0413':'Gcy','\u0453':'gjcy','\u0403':'GJcy','\u0434':'dcy','\u0414':'Dcy','\u0452':'djcy','\u0402':'DJcy','\u0435':'iecy','\u0415':'IEcy','\u0451':'iocy','\u0401':'IOcy','\u0454':'jukcy','\u0404':'Jukcy','\u0436':'zhcy','\u0416':'ZHcy','\u0437':'zcy','\u0417':'Zcy','\u0455':'dscy','\u0405':'DScy','\u0438':'icy','\u0418':'Icy','\u0456':'iukcy','\u0406':'Iukcy','\u0457':'yicy','\u0407':'YIcy','\u0439':'jcy','\u0419':'Jcy','\u0458':'jsercy','\u0408':'Jsercy','\u043A':'kcy','\u041A':'Kcy','\u045C':'kjcy','\u040C':'KJcy','\u043B':'lcy','\u041B':'Lcy','\u0459':'ljcy','\u0409':'LJcy','\u043C':'mcy','\u041C':'Mcy','\u043D':'ncy','\u041D':'Ncy','\u045A':'njcy','\u040A':'NJcy','\u043E':'ocy','\u041E':'Ocy','\u043F':'pcy','\u041F':'Pcy','\u0440':'rcy','\u0420':'Rcy','\u0441':'scy','\u0421':'Scy','\u0442':'tcy','\u0422':'Tcy','\u045B':'tshcy','\u040B':'TSHcy','\u0443':'ucy','\u0423':'Ucy','\u045E':'ubrcy','\u040E':'Ubrcy','\u0444':'fcy','\u0424':'Fcy','\u0445':'khcy','\u0425':'KHcy','\u0446':'tscy','\u0426':'TScy','\u0447':'chcy','\u0427':'CHcy','\u045F':'dzcy','\u040F':'DZcy','\u0448':'shcy','\u0428':'SHcy','\u0449':'shchcy','\u0429':'SHCHcy','\u044A':'hardcy','\u042A':'HARDcy','\u044B':'ycy','\u042B':'Ycy','\u044C':'softcy','\u042C':'SOFTcy','\u044D':'ecy','\u042D':'Ecy','\u044E':'yucy','\u042E':'YUcy','\u044F':'yacy','\u042F':'YAcy','\u2135':'aleph','\u2136':'beth','\u2137':'gimel','\u2138':'daleth'}; + + var regexEscape = /["&'<>`]/g; + var escapeMap = { + '"': '"', + '&': '&', + '\'': ''', + '<': '<', + // See https://mathiasbynens.be/notes/ambiguous-ampersands: in HTML, the + // following is not strictly necessary unless itโ€™s part of a tag or an + // unquoted attribute value. Weโ€™re only escaping it to support those + // situations, and for XML support. + '>': '>', + // In Internet Explorer โ‰ค 8, the backtick character can be used + // to break out of (un)quoted attribute values or HTML comments. + // See http://html5sec.org/#102, http://html5sec.org/#108, and + // http://html5sec.org/#133. + '`': '`' + }; + + var regexInvalidEntity = /&#(?:[xX][^a-fA-F0-9]|[^0-9xX])/; + var regexInvalidRawCodePoint = /[\0-\x08\x0B\x0E-\x1F\x7F-\x9F\uFDD0-\uFDEF\uFFFE\uFFFF]|[\uD83F\uD87F\uD8BF\uD8FF\uD93F\uD97F\uD9BF\uD9FF\uDA3F\uDA7F\uDABF\uDAFF\uDB3F\uDB7F\uDBBF\uDBFF][\uDFFE\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/; + var regexDecode = /&(CounterClockwiseContourIntegral|DoubleLongLeftRightArrow|ClockwiseContourIntegral|NotNestedGreaterGreater|NotSquareSupersetEqual|DiacriticalDoubleAcute|NotRightTriangleEqual|NotSucceedsSlantEqual|NotPrecedesSlantEqual|CloseCurlyDoubleQuote|NegativeVeryThinSpace|DoubleContourIntegral|FilledVerySmallSquare|CapitalDifferentialD|OpenCurlyDoubleQuote|EmptyVerySmallSquare|NestedGreaterGreater|DoubleLongRightArrow|NotLeftTriangleEqual|NotGreaterSlantEqual|ReverseUpEquilibrium|DoubleLeftRightArrow|NotSquareSubsetEqual|NotDoubleVerticalBar|RightArrowLeftArrow|NotGreaterFullEqual|NotRightTriangleBar|SquareSupersetEqual|DownLeftRightVector|DoubleLongLeftArrow|leftrightsquigarrow|LeftArrowRightArrow|NegativeMediumSpace|blacktriangleright|RightDownVectorBar|PrecedesSlantEqual|RightDoubleBracket|SucceedsSlantEqual|NotLeftTriangleBar|RightTriangleEqual|SquareIntersection|RightDownTeeVector|ReverseEquilibrium|NegativeThickSpace|longleftrightarrow|Longleftrightarrow|LongLeftRightArrow|DownRightTeeVector|DownRightVectorBar|GreaterSlantEqual|SquareSubsetEqual|LeftDownVectorBar|LeftDoubleBracket|VerticalSeparator|rightleftharpoons|NotGreaterGreater|NotSquareSuperset|blacktriangleleft|blacktriangledown|NegativeThinSpace|LeftDownTeeVector|NotLessSlantEqual|leftrightharpoons|DoubleUpDownArrow|DoubleVerticalBar|LeftTriangleEqual|FilledSmallSquare|twoheadrightarrow|NotNestedLessLess|DownLeftTeeVector|DownLeftVectorBar|RightAngleBracket|NotTildeFullEqual|NotReverseElement|RightUpDownVector|DiacriticalTilde|NotSucceedsTilde|circlearrowright|NotPrecedesEqual|rightharpoondown|DoubleRightArrow|NotSucceedsEqual|NonBreakingSpace|NotRightTriangle|LessEqualGreater|RightUpTeeVector|LeftAngleBracket|GreaterFullEqual|DownArrowUpArrow|RightUpVectorBar|twoheadleftarrow|GreaterEqualLess|downharpoonright|RightTriangleBar|ntrianglerighteq|NotSupersetEqual|LeftUpDownVector|DiacriticalAcute|rightrightarrows|vartriangleright|UpArrowDownArrow|DiacriticalGrave|UnderParenthesis|EmptySmallSquare|LeftUpVectorBar|leftrightarrows|DownRightVector|downharpoonleft|trianglerighteq|ShortRightArrow|OverParenthesis|DoubleLeftArrow|DoubleDownArrow|NotSquareSubset|bigtriangledown|ntrianglelefteq|UpperRightArrow|curvearrowright|vartriangleleft|NotLeftTriangle|nleftrightarrow|LowerRightArrow|NotHumpDownHump|NotGreaterTilde|rightthreetimes|LeftUpTeeVector|NotGreaterEqual|straightepsilon|LeftTriangleBar|rightsquigarrow|ContourIntegral|rightleftarrows|CloseCurlyQuote|RightDownVector|LeftRightVector|nLeftrightarrow|leftharpoondown|circlearrowleft|SquareSuperset|OpenCurlyQuote|hookrightarrow|HorizontalLine|DiacriticalDot|NotLessGreater|ntriangleright|DoubleRightTee|InvisibleComma|InvisibleTimes|LowerLeftArrow|DownLeftVector|NotSubsetEqual|curvearrowleft|trianglelefteq|NotVerticalBar|TildeFullEqual|downdownarrows|NotGreaterLess|RightTeeVector|ZeroWidthSpace|looparrowright|LongRightArrow|doublebarwedge|ShortLeftArrow|ShortDownArrow|RightVectorBar|GreaterGreater|ReverseElement|rightharpoonup|LessSlantEqual|leftthreetimes|upharpoonright|rightarrowtail|LeftDownVector|Longrightarrow|NestedLessLess|UpperLeftArrow|nshortparallel|leftleftarrows|leftrightarrow|Leftrightarrow|LeftRightArrow|longrightarrow|upharpoonleft|RightArrowBar|ApplyFunction|LeftTeeVector|leftarrowtail|NotEqualTilde|varsubsetneqq|varsupsetneqq|RightTeeArrow|SucceedsEqual|SucceedsTilde|LeftVectorBar|SupersetEqual|hookleftarrow|DifferentialD|VerticalTilde|VeryThinSpace|blacktriangle|bigtriangleup|LessFullEqual|divideontimes|leftharpoonup|UpEquilibrium|ntriangleleft|RightTriangle|measuredangle|shortparallel|longleftarrow|Longleftarrow|LongLeftArrow|DoubleLeftTee|Poincareplane|PrecedesEqual|triangleright|DoubleUpArrow|RightUpVector|fallingdotseq|looparrowleft|PrecedesTilde|NotTildeEqual|NotTildeTilde|smallsetminus|Proportional|triangleleft|triangledown|UnderBracket|NotHumpEqual|exponentiale|ExponentialE|NotLessTilde|HilbertSpace|RightCeiling|blacklozenge|varsupsetneq|HumpDownHump|GreaterEqual|VerticalLine|LeftTeeArrow|NotLessEqual|DownTeeArrow|LeftTriangle|varsubsetneq|Intersection|NotCongruent|DownArrowBar|LeftUpVector|LeftArrowBar|risingdotseq|GreaterTilde|RoundImplies|SquareSubset|ShortUpArrow|NotSuperset|quaternions|precnapprox|backepsilon|preccurlyeq|OverBracket|blacksquare|MediumSpace|VerticalBar|circledcirc|circleddash|CircleMinus|CircleTimes|LessGreater|curlyeqprec|curlyeqsucc|diamondsuit|UpDownArrow|Updownarrow|RuleDelayed|Rrightarrow|updownarrow|RightVector|nRightarrow|nrightarrow|eqslantless|LeftCeiling|Equilibrium|SmallCircle|expectation|NotSucceeds|thickapprox|GreaterLess|SquareUnion|NotPrecedes|NotLessLess|straightphi|succnapprox|succcurlyeq|SubsetEqual|sqsupseteq|Proportion|Laplacetrf|ImaginaryI|supsetneqq|NotGreater|gtreqqless|NotElement|ThickSpace|TildeEqual|TildeTilde|Fouriertrf|rmoustache|EqualTilde|eqslantgtr|UnderBrace|LeftVector|UpArrowBar|nLeftarrow|nsubseteqq|subsetneqq|nsupseteqq|nleftarrow|succapprox|lessapprox|UpTeeArrow|upuparrows|curlywedge|lesseqqgtr|varepsilon|varnothing|RightFloor|complement|CirclePlus|sqsubseteq|Lleftarrow|circledast|RightArrow|Rightarrow|rightarrow|lmoustache|Bernoullis|precapprox|mapstoleft|mapstodown|longmapsto|dotsquare|downarrow|DoubleDot|nsubseteq|supsetneq|leftarrow|nsupseteq|subsetneq|ThinSpace|ngeqslant|subseteqq|HumpEqual|NotSubset|triangleq|NotCupCap|lesseqgtr|heartsuit|TripleDot|Leftarrow|Coproduct|Congruent|varpropto|complexes|gvertneqq|LeftArrow|LessTilde|supseteqq|MinusPlus|CircleDot|nleqslant|NotExists|gtreqless|nparallel|UnionPlus|LeftFloor|checkmark|CenterDot|centerdot|Mellintrf|gtrapprox|bigotimes|OverBrace|spadesuit|therefore|pitchfork|rationals|PlusMinus|Backslash|Therefore|DownBreve|backsimeq|backprime|DownArrow|nshortmid|Downarrow|lvertneqq|eqvparsl|imagline|imagpart|infintie|integers|Integral|intercal|LessLess|Uarrocir|intlarhk|sqsupset|angmsdaf|sqsubset|llcorner|vartheta|cupbrcap|lnapprox|Superset|SuchThat|succnsim|succneqq|angmsdag|biguplus|curlyvee|trpezium|Succeeds|NotTilde|bigwedge|angmsdah|angrtvbd|triminus|cwconint|fpartint|lrcorner|smeparsl|subseteq|urcorner|lurdshar|laemptyv|DDotrahd|approxeq|ldrushar|awconint|mapstoup|backcong|shortmid|triangle|geqslant|gesdotol|timesbar|circledR|circledS|setminus|multimap|naturals|scpolint|ncongdot|RightTee|boxminus|gnapprox|boxtimes|andslope|thicksim|angmsdaa|varsigma|cirfnint|rtriltri|angmsdab|rppolint|angmsdac|barwedge|drbkarow|clubsuit|thetasym|bsolhsub|capbrcup|dzigrarr|doteqdot|DotEqual|dotminus|UnderBar|NotEqual|realpart|otimesas|ulcorner|hksearow|hkswarow|parallel|PartialD|elinters|emptyset|plusacir|bbrktbrk|angmsdad|pointint|bigoplus|angmsdae|Precedes|bigsqcup|varkappa|notindot|supseteq|precneqq|precnsim|profalar|profline|profsurf|leqslant|lesdotor|raemptyv|subplus|notnivb|notnivc|subrarr|zigrarr|vzigzag|submult|subedot|Element|between|cirscir|larrbfs|larrsim|lotimes|lbrksld|lbrkslu|lozenge|ldrdhar|dbkarow|bigcirc|epsilon|simrarr|simplus|ltquest|Epsilon|luruhar|gtquest|maltese|npolint|eqcolon|npreceq|bigodot|ddagger|gtrless|bnequiv|harrcir|ddotseq|equivDD|backsim|demptyv|nsqsube|nsqsupe|Upsilon|nsubset|upsilon|minusdu|nsucceq|swarrow|nsupset|coloneq|searrow|boxplus|napprox|natural|asympeq|alefsym|congdot|nearrow|bigstar|diamond|supplus|tritime|LeftTee|nvinfin|triplus|NewLine|nvltrie|nvrtrie|nwarrow|nexists|Diamond|ruluhar|Implies|supmult|angzarr|suplarr|suphsub|questeq|because|digamma|Because|olcross|bemptyv|omicron|Omicron|rotimes|NoBreak|intprod|angrtvb|orderof|uwangle|suphsol|lesdoto|orslope|DownTee|realine|cudarrl|rdldhar|OverBar|supedot|lessdot|supdsub|topfork|succsim|rbrkslu|rbrksld|pertenk|cudarrr|isindot|planckh|lessgtr|pluscir|gesdoto|plussim|plustwo|lesssim|cularrp|rarrsim|Cayleys|notinva|notinvb|notinvc|UpArrow|Uparrow|uparrow|NotLess|dwangle|precsim|Product|curarrm|Cconint|dotplus|rarrbfs|ccupssm|Cedilla|cemptyv|notniva|quatint|frac35|frac38|frac45|frac56|frac58|frac78|tridot|xoplus|gacute|gammad|Gammad|lfisht|lfloor|bigcup|sqsupe|gbreve|Gbreve|lharul|sqsube|sqcups|Gcedil|apacir|llhard|lmidot|Lmidot|lmoust|andand|sqcaps|approx|Abreve|spades|circeq|tprime|divide|topcir|Assign|topbot|gesdot|divonx|xuplus|timesd|gesles|atilde|solbar|SOFTcy|loplus|timesb|lowast|lowbar|dlcorn|dlcrop|softcy|dollar|lparlt|thksim|lrhard|Atilde|lsaquo|smashp|bigvee|thinsp|wreath|bkarow|lsquor|lstrok|Lstrok|lthree|ltimes|ltlarr|DotDot|simdot|ltrPar|weierp|xsqcup|angmsd|sigmav|sigmaf|zeetrf|Zcaron|zcaron|mapsto|vsupne|thetav|cirmid|marker|mcomma|Zacute|vsubnE|there4|gtlPar|vsubne|bottom|gtrarr|SHCHcy|shchcy|midast|midcir|middot|minusb|minusd|gtrdot|bowtie|sfrown|mnplus|models|colone|seswar|Colone|mstpos|searhk|gtrsim|nacute|Nacute|boxbox|telrec|hairsp|Tcedil|nbumpe|scnsim|ncaron|Ncaron|ncedil|Ncedil|hamilt|Scedil|nearhk|hardcy|HARDcy|tcedil|Tcaron|commat|nequiv|nesear|tcaron|target|hearts|nexist|varrho|scedil|Scaron|scaron|hellip|Sacute|sacute|hercon|swnwar|compfn|rtimes|rthree|rsquor|rsaquo|zacute|wedgeq|homtht|barvee|barwed|Barwed|rpargt|horbar|conint|swarhk|roplus|nltrie|hslash|hstrok|Hstrok|rmoust|Conint|bprime|hybull|hyphen|iacute|Iacute|supsup|supsub|supsim|varphi|coprod|brvbar|agrave|Supset|supset|igrave|Igrave|notinE|Agrave|iiiint|iinfin|copysr|wedbar|Verbar|vangrt|becaus|incare|verbar|inodot|bullet|drcorn|intcal|drcrop|cularr|vellip|Utilde|bumpeq|cupcap|dstrok|Dstrok|CupCap|cupcup|cupdot|eacute|Eacute|supdot|iquest|easter|ecaron|Ecaron|ecolon|isinsv|utilde|itilde|Itilde|curarr|succeq|Bumpeq|cacute|ulcrop|nparsl|Cacute|nprcue|egrave|Egrave|nrarrc|nrarrw|subsup|subsub|nrtrie|jsercy|nsccue|Jsercy|kappav|kcedil|Kcedil|subsim|ulcorn|nsimeq|egsdot|veebar|kgreen|capand|elsdot|Subset|subset|curren|aacute|lacute|Lacute|emptyv|ntilde|Ntilde|lagran|lambda|Lambda|capcap|Ugrave|langle|subdot|emsp13|numero|emsp14|nvdash|nvDash|nVdash|nVDash|ugrave|ufisht|nvHarr|larrfs|nvlArr|larrhk|larrlp|larrpl|nvrArr|Udblac|nwarhk|larrtl|nwnear|oacute|Oacute|latail|lAtail|sstarf|lbrace|odblac|Odblac|lbrack|udblac|odsold|eparsl|lcaron|Lcaron|ograve|Ograve|lcedil|Lcedil|Aacute|ssmile|ssetmn|squarf|ldquor|capcup|ominus|cylcty|rharul|eqcirc|dagger|rfloor|rfisht|Dagger|daleth|equals|origof|capdot|equest|dcaron|Dcaron|rdquor|oslash|Oslash|otilde|Otilde|otimes|Otimes|urcrop|Ubreve|ubreve|Yacute|Uacute|uacute|Rcedil|rcedil|urcorn|parsim|Rcaron|Vdashl|rcaron|Tstrok|percnt|period|permil|Exists|yacute|rbrack|rbrace|phmmat|ccaron|Ccaron|planck|ccedil|plankv|tstrok|female|plusdo|plusdu|ffilig|plusmn|ffllig|Ccedil|rAtail|dfisht|bernou|ratail|Rarrtl|rarrtl|angsph|rarrpl|rarrlp|rarrhk|xwedge|xotime|forall|ForAll|Vvdash|vsupnE|preceq|bigcap|frac12|frac13|frac14|primes|rarrfs|prnsim|frac15|Square|frac16|square|lesdot|frac18|frac23|propto|prurel|rarrap|rangle|puncsp|frac25|Racute|qprime|racute|lesges|frac34|abreve|AElig|eqsim|utdot|setmn|urtri|Equal|Uring|seArr|uring|searr|dashv|Dashv|mumap|nabla|iogon|Iogon|sdote|sdotb|scsim|napid|napos|equiv|natur|Acirc|dblac|erarr|nbump|iprod|erDot|ucirc|awint|esdot|angrt|ncong|isinE|scnap|Scirc|scirc|ndash|isins|Ubrcy|nearr|neArr|isinv|nedot|ubrcy|acute|Ycirc|iukcy|Iukcy|xutri|nesim|caret|jcirc|Jcirc|caron|twixt|ddarr|sccue|exist|jmath|sbquo|ngeqq|angst|ccaps|lceil|ngsim|UpTee|delta|Delta|rtrif|nharr|nhArr|nhpar|rtrie|jukcy|Jukcy|kappa|rsquo|Kappa|nlarr|nlArr|TSHcy|rrarr|aogon|Aogon|fflig|xrarr|tshcy|ccirc|nleqq|filig|upsih|nless|dharl|nlsim|fjlig|ropar|nltri|dharr|robrk|roarr|fllig|fltns|roang|rnmid|subnE|subne|lAarr|trisb|Ccirc|acirc|ccups|blank|VDash|forkv|Vdash|langd|cedil|blk12|blk14|laquo|strns|diams|notin|vDash|larrb|blk34|block|disin|uplus|vdash|vBarv|aelig|starf|Wedge|check|xrArr|lates|lbarr|lBarr|notni|lbbrk|bcong|frasl|lbrke|frown|vrtri|vprop|vnsup|gamma|Gamma|wedge|xodot|bdquo|srarr|doteq|ldquo|boxdl|boxdL|gcirc|Gcirc|boxDl|boxDL|boxdr|boxdR|boxDr|TRADE|trade|rlhar|boxDR|vnsub|npart|vltri|rlarr|boxhd|boxhD|nprec|gescc|nrarr|nrArr|boxHd|boxHD|boxhu|boxhU|nrtri|boxHu|clubs|boxHU|times|colon|Colon|gimel|xlArr|Tilde|nsime|tilde|nsmid|nspar|THORN|thorn|xlarr|nsube|nsubE|thkap|xhArr|comma|nsucc|boxul|boxuL|nsupe|nsupE|gneqq|gnsim|boxUl|boxUL|grave|boxur|boxuR|boxUr|boxUR|lescc|angle|bepsi|boxvh|varpi|boxvH|numsp|Theta|gsime|gsiml|theta|boxVh|boxVH|boxvl|gtcir|gtdot|boxvL|boxVl|boxVL|crarr|cross|Cross|nvsim|boxvr|nwarr|nwArr|sqsup|dtdot|Uogon|lhard|lharu|dtrif|ocirc|Ocirc|lhblk|duarr|odash|sqsub|Hacek|sqcup|llarr|duhar|oelig|OElig|ofcir|boxvR|uogon|lltri|boxVr|csube|uuarr|ohbar|csupe|ctdot|olarr|olcir|harrw|oline|sqcap|omacr|Omacr|omega|Omega|boxVR|aleph|lneqq|lnsim|loang|loarr|rharu|lobrk|hcirc|operp|oplus|rhard|Hcirc|orarr|Union|order|ecirc|Ecirc|cuepr|szlig|cuesc|breve|reals|eDDot|Breve|hoarr|lopar|utrif|rdquo|Umacr|umacr|efDot|swArr|ultri|alpha|rceil|ovbar|swarr|Wcirc|wcirc|smtes|smile|bsemi|lrarr|aring|parsl|lrhar|bsime|uhblk|lrtri|cupor|Aring|uharr|uharl|slarr|rbrke|bsolb|lsime|rbbrk|RBarr|lsimg|phone|rBarr|rbarr|icirc|lsquo|Icirc|emacr|Emacr|ratio|simne|plusb|simlE|simgE|simeq|pluse|ltcir|ltdot|empty|xharr|xdtri|iexcl|Alpha|ltrie|rarrw|pound|ltrif|xcirc|bumpe|prcue|bumpE|asymp|amacr|cuvee|Sigma|sigma|iiint|udhar|iiota|ijlig|IJlig|supnE|imacr|Imacr|prime|Prime|image|prnap|eogon|Eogon|rarrc|mdash|mDDot|cuwed|imath|supne|imped|Amacr|udarr|prsim|micro|rarrb|cwint|raquo|infin|eplus|range|rangd|Ucirc|radic|minus|amalg|veeeq|rAarr|epsiv|ycirc|quest|sharp|quot|zwnj|Qscr|race|qscr|Qopf|qopf|qint|rang|Rang|Zscr|zscr|Zopf|zopf|rarr|rArr|Rarr|Pscr|pscr|prop|prod|prnE|prec|ZHcy|zhcy|prap|Zeta|zeta|Popf|popf|Zdot|plus|zdot|Yuml|yuml|phiv|YUcy|yucy|Yscr|yscr|perp|Yopf|yopf|part|para|YIcy|Ouml|rcub|yicy|YAcy|rdca|ouml|osol|Oscr|rdsh|yacy|real|oscr|xvee|andd|rect|andv|Xscr|oror|ordm|ordf|xscr|ange|aopf|Aopf|rHar|Xopf|opar|Oopf|xopf|xnis|rhov|oopf|omid|xmap|oint|apid|apos|ogon|ascr|Ascr|odot|odiv|xcup|xcap|ocir|oast|nvlt|nvle|nvgt|nvge|nvap|Wscr|wscr|auml|ntlg|ntgl|nsup|nsub|nsim|Nscr|nscr|nsce|Wopf|ring|npre|wopf|npar|Auml|Barv|bbrk|Nopf|nopf|nmid|nLtv|beta|ropf|Ropf|Beta|beth|nles|rpar|nleq|bnot|bNot|nldr|NJcy|rscr|Rscr|Vscr|vscr|rsqb|njcy|bopf|nisd|Bopf|rtri|Vopf|nGtv|ngtr|vopf|boxh|boxH|boxv|nges|ngeq|boxV|bscr|scap|Bscr|bsim|Vert|vert|bsol|bull|bump|caps|cdot|ncup|scnE|ncap|nbsp|napE|Cdot|cent|sdot|Vbar|nang|vBar|chcy|Mscr|mscr|sect|semi|CHcy|Mopf|mopf|sext|circ|cire|mldr|mlcp|cirE|comp|shcy|SHcy|vArr|varr|cong|copf|Copf|copy|COPY|malt|male|macr|lvnE|cscr|ltri|sime|ltcc|simg|Cscr|siml|csub|Uuml|lsqb|lsim|uuml|csup|Lscr|lscr|utri|smid|lpar|cups|smte|lozf|darr|Lopf|Uscr|solb|lopf|sopf|Sopf|lneq|uscr|spar|dArr|lnap|Darr|dash|Sqrt|LJcy|ljcy|lHar|dHar|Upsi|upsi|diam|lesg|djcy|DJcy|leqq|dopf|Dopf|dscr|Dscr|dscy|ldsh|ldca|squf|DScy|sscr|Sscr|dsol|lcub|late|star|Star|Uopf|Larr|lArr|larr|uopf|dtri|dzcy|sube|subE|Lang|lang|Kscr|kscr|Kopf|kopf|KJcy|kjcy|KHcy|khcy|DZcy|ecir|edot|eDot|Jscr|jscr|succ|Jopf|jopf|Edot|uHar|emsp|ensp|Iuml|iuml|eopf|isin|Iscr|iscr|Eopf|epar|sung|epsi|escr|sup1|sup2|sup3|Iota|iota|supe|supE|Iopf|iopf|IOcy|iocy|Escr|esim|Esim|imof|Uarr|QUOT|uArr|uarr|euml|IEcy|iecy|Idot|Euml|euro|excl|Hscr|hscr|Hopf|hopf|TScy|tscy|Tscr|hbar|tscr|flat|tbrk|fnof|hArr|harr|half|fopf|Fopf|tdot|gvnE|fork|trie|gtcc|fscr|Fscr|gdot|gsim|Gscr|gscr|Gopf|gopf|gneq|Gdot|tosa|gnap|Topf|topf|geqq|toea|GJcy|gjcy|tint|gesl|mid|Sfr|ggg|top|ges|gla|glE|glj|geq|gne|gEl|gel|gnE|Gcy|gcy|gap|Tfr|tfr|Tcy|tcy|Hat|Tau|Ffr|tau|Tab|hfr|Hfr|ffr|Fcy|fcy|icy|Icy|iff|ETH|eth|ifr|Ifr|Eta|eta|int|Int|Sup|sup|ucy|Ucy|Sum|sum|jcy|ENG|ufr|Ufr|eng|Jcy|jfr|els|ell|egs|Efr|efr|Jfr|uml|kcy|Kcy|Ecy|ecy|kfr|Kfr|lap|Sub|sub|lat|lcy|Lcy|leg|Dot|dot|lEg|leq|les|squ|div|die|lfr|Lfr|lgE|Dfr|dfr|Del|deg|Dcy|dcy|lne|lnE|sol|loz|smt|Cup|lrm|cup|lsh|Lsh|sim|shy|map|Map|mcy|Mcy|mfr|Mfr|mho|gfr|Gfr|sfr|cir|Chi|chi|nap|Cfr|vcy|Vcy|cfr|Scy|scy|ncy|Ncy|vee|Vee|Cap|cap|nfr|scE|sce|Nfr|nge|ngE|nGg|vfr|Vfr|ngt|bot|nGt|nis|niv|Rsh|rsh|nle|nlE|bne|Bfr|bfr|nLl|nlt|nLt|Bcy|bcy|not|Not|rlm|wfr|Wfr|npr|nsc|num|ocy|ast|Ocy|ofr|xfr|Xfr|Ofr|ogt|ohm|apE|olt|Rho|ape|rho|Rfr|rfr|ord|REG|ang|reg|orv|And|and|AMP|Rcy|amp|Afr|ycy|Ycy|yen|yfr|Yfr|rcy|par|pcy|Pcy|pfr|Pfr|phi|Phi|afr|Acy|acy|zcy|Zcy|piv|acE|acd|zfr|Zfr|pre|prE|psi|Psi|qfr|Qfr|zwj|Or|ge|Gg|gt|gg|el|oS|lt|Lt|LT|Re|lg|gl|eg|ne|Im|it|le|DD|wp|wr|nu|Nu|dd|lE|Sc|sc|pi|Pi|ee|af|ll|Ll|rx|gE|xi|pm|Xi|ic|pr|Pr|in|ni|mp|mu|ac|Mu|or|ap|Gt|GT|ii);|&(Aacute|Agrave|Atilde|Ccedil|Eacute|Egrave|Iacute|Igrave|Ntilde|Oacute|Ograve|Oslash|Otilde|Uacute|Ugrave|Yacute|aacute|agrave|atilde|brvbar|ccedil|curren|divide|eacute|egrave|frac12|frac14|frac34|iacute|igrave|iquest|middot|ntilde|oacute|ograve|oslash|otilde|plusmn|uacute|ugrave|yacute|AElig|Acirc|Aring|Ecirc|Icirc|Ocirc|THORN|Ucirc|acirc|acute|aelig|aring|cedil|ecirc|icirc|iexcl|laquo|micro|ocirc|pound|raquo|szlig|thorn|times|ucirc|Auml|COPY|Euml|Iuml|Ouml|QUOT|Uuml|auml|cent|copy|euml|iuml|macr|nbsp|ordf|ordm|ouml|para|quot|sect|sup1|sup2|sup3|uuml|yuml|AMP|ETH|REG|amp|deg|eth|not|reg|shy|uml|yen|GT|LT|gt|lt)(?!;)([=a-zA-Z0-9]?)|&#([0-9]+)(;?)|&#[xX]([a-fA-F0-9]+)(;?)|&([0-9a-zA-Z]+)/g; + var decodeMap = {'aacute':'\xE1','Aacute':'\xC1','abreve':'\u0103','Abreve':'\u0102','ac':'\u223E','acd':'\u223F','acE':'\u223E\u0333','acirc':'\xE2','Acirc':'\xC2','acute':'\xB4','acy':'\u0430','Acy':'\u0410','aelig':'\xE6','AElig':'\xC6','af':'\u2061','afr':'\uD835\uDD1E','Afr':'\uD835\uDD04','agrave':'\xE0','Agrave':'\xC0','alefsym':'\u2135','aleph':'\u2135','alpha':'\u03B1','Alpha':'\u0391','amacr':'\u0101','Amacr':'\u0100','amalg':'\u2A3F','amp':'&','AMP':'&','and':'\u2227','And':'\u2A53','andand':'\u2A55','andd':'\u2A5C','andslope':'\u2A58','andv':'\u2A5A','ang':'\u2220','ange':'\u29A4','angle':'\u2220','angmsd':'\u2221','angmsdaa':'\u29A8','angmsdab':'\u29A9','angmsdac':'\u29AA','angmsdad':'\u29AB','angmsdae':'\u29AC','angmsdaf':'\u29AD','angmsdag':'\u29AE','angmsdah':'\u29AF','angrt':'\u221F','angrtvb':'\u22BE','angrtvbd':'\u299D','angsph':'\u2222','angst':'\xC5','angzarr':'\u237C','aogon':'\u0105','Aogon':'\u0104','aopf':'\uD835\uDD52','Aopf':'\uD835\uDD38','ap':'\u2248','apacir':'\u2A6F','ape':'\u224A','apE':'\u2A70','apid':'\u224B','apos':'\'','ApplyFunction':'\u2061','approx':'\u2248','approxeq':'\u224A','aring':'\xE5','Aring':'\xC5','ascr':'\uD835\uDCB6','Ascr':'\uD835\uDC9C','Assign':'\u2254','ast':'*','asymp':'\u2248','asympeq':'\u224D','atilde':'\xE3','Atilde':'\xC3','auml':'\xE4','Auml':'\xC4','awconint':'\u2233','awint':'\u2A11','backcong':'\u224C','backepsilon':'\u03F6','backprime':'\u2035','backsim':'\u223D','backsimeq':'\u22CD','Backslash':'\u2216','Barv':'\u2AE7','barvee':'\u22BD','barwed':'\u2305','Barwed':'\u2306','barwedge':'\u2305','bbrk':'\u23B5','bbrktbrk':'\u23B6','bcong':'\u224C','bcy':'\u0431','Bcy':'\u0411','bdquo':'\u201E','becaus':'\u2235','because':'\u2235','Because':'\u2235','bemptyv':'\u29B0','bepsi':'\u03F6','bernou':'\u212C','Bernoullis':'\u212C','beta':'\u03B2','Beta':'\u0392','beth':'\u2136','between':'\u226C','bfr':'\uD835\uDD1F','Bfr':'\uD835\uDD05','bigcap':'\u22C2','bigcirc':'\u25EF','bigcup':'\u22C3','bigodot':'\u2A00','bigoplus':'\u2A01','bigotimes':'\u2A02','bigsqcup':'\u2A06','bigstar':'\u2605','bigtriangledown':'\u25BD','bigtriangleup':'\u25B3','biguplus':'\u2A04','bigvee':'\u22C1','bigwedge':'\u22C0','bkarow':'\u290D','blacklozenge':'\u29EB','blacksquare':'\u25AA','blacktriangle':'\u25B4','blacktriangledown':'\u25BE','blacktriangleleft':'\u25C2','blacktriangleright':'\u25B8','blank':'\u2423','blk12':'\u2592','blk14':'\u2591','blk34':'\u2593','block':'\u2588','bne':'=\u20E5','bnequiv':'\u2261\u20E5','bnot':'\u2310','bNot':'\u2AED','bopf':'\uD835\uDD53','Bopf':'\uD835\uDD39','bot':'\u22A5','bottom':'\u22A5','bowtie':'\u22C8','boxbox':'\u29C9','boxdl':'\u2510','boxdL':'\u2555','boxDl':'\u2556','boxDL':'\u2557','boxdr':'\u250C','boxdR':'\u2552','boxDr':'\u2553','boxDR':'\u2554','boxh':'\u2500','boxH':'\u2550','boxhd':'\u252C','boxhD':'\u2565','boxHd':'\u2564','boxHD':'\u2566','boxhu':'\u2534','boxhU':'\u2568','boxHu':'\u2567','boxHU':'\u2569','boxminus':'\u229F','boxplus':'\u229E','boxtimes':'\u22A0','boxul':'\u2518','boxuL':'\u255B','boxUl':'\u255C','boxUL':'\u255D','boxur':'\u2514','boxuR':'\u2558','boxUr':'\u2559','boxUR':'\u255A','boxv':'\u2502','boxV':'\u2551','boxvh':'\u253C','boxvH':'\u256A','boxVh':'\u256B','boxVH':'\u256C','boxvl':'\u2524','boxvL':'\u2561','boxVl':'\u2562','boxVL':'\u2563','boxvr':'\u251C','boxvR':'\u255E','boxVr':'\u255F','boxVR':'\u2560','bprime':'\u2035','breve':'\u02D8','Breve':'\u02D8','brvbar':'\xA6','bscr':'\uD835\uDCB7','Bscr':'\u212C','bsemi':'\u204F','bsim':'\u223D','bsime':'\u22CD','bsol':'\\','bsolb':'\u29C5','bsolhsub':'\u27C8','bull':'\u2022','bullet':'\u2022','bump':'\u224E','bumpe':'\u224F','bumpE':'\u2AAE','bumpeq':'\u224F','Bumpeq':'\u224E','cacute':'\u0107','Cacute':'\u0106','cap':'\u2229','Cap':'\u22D2','capand':'\u2A44','capbrcup':'\u2A49','capcap':'\u2A4B','capcup':'\u2A47','capdot':'\u2A40','CapitalDifferentialD':'\u2145','caps':'\u2229\uFE00','caret':'\u2041','caron':'\u02C7','Cayleys':'\u212D','ccaps':'\u2A4D','ccaron':'\u010D','Ccaron':'\u010C','ccedil':'\xE7','Ccedil':'\xC7','ccirc':'\u0109','Ccirc':'\u0108','Cconint':'\u2230','ccups':'\u2A4C','ccupssm':'\u2A50','cdot':'\u010B','Cdot':'\u010A','cedil':'\xB8','Cedilla':'\xB8','cemptyv':'\u29B2','cent':'\xA2','centerdot':'\xB7','CenterDot':'\xB7','cfr':'\uD835\uDD20','Cfr':'\u212D','chcy':'\u0447','CHcy':'\u0427','check':'\u2713','checkmark':'\u2713','chi':'\u03C7','Chi':'\u03A7','cir':'\u25CB','circ':'\u02C6','circeq':'\u2257','circlearrowleft':'\u21BA','circlearrowright':'\u21BB','circledast':'\u229B','circledcirc':'\u229A','circleddash':'\u229D','CircleDot':'\u2299','circledR':'\xAE','circledS':'\u24C8','CircleMinus':'\u2296','CirclePlus':'\u2295','CircleTimes':'\u2297','cire':'\u2257','cirE':'\u29C3','cirfnint':'\u2A10','cirmid':'\u2AEF','cirscir':'\u29C2','ClockwiseContourIntegral':'\u2232','CloseCurlyDoubleQuote':'\u201D','CloseCurlyQuote':'\u2019','clubs':'\u2663','clubsuit':'\u2663','colon':':','Colon':'\u2237','colone':'\u2254','Colone':'\u2A74','coloneq':'\u2254','comma':',','commat':'@','comp':'\u2201','compfn':'\u2218','complement':'\u2201','complexes':'\u2102','cong':'\u2245','congdot':'\u2A6D','Congruent':'\u2261','conint':'\u222E','Conint':'\u222F','ContourIntegral':'\u222E','copf':'\uD835\uDD54','Copf':'\u2102','coprod':'\u2210','Coproduct':'\u2210','copy':'\xA9','COPY':'\xA9','copysr':'\u2117','CounterClockwiseContourIntegral':'\u2233','crarr':'\u21B5','cross':'\u2717','Cross':'\u2A2F','cscr':'\uD835\uDCB8','Cscr':'\uD835\uDC9E','csub':'\u2ACF','csube':'\u2AD1','csup':'\u2AD0','csupe':'\u2AD2','ctdot':'\u22EF','cudarrl':'\u2938','cudarrr':'\u2935','cuepr':'\u22DE','cuesc':'\u22DF','cularr':'\u21B6','cularrp':'\u293D','cup':'\u222A','Cup':'\u22D3','cupbrcap':'\u2A48','cupcap':'\u2A46','CupCap':'\u224D','cupcup':'\u2A4A','cupdot':'\u228D','cupor':'\u2A45','cups':'\u222A\uFE00','curarr':'\u21B7','curarrm':'\u293C','curlyeqprec':'\u22DE','curlyeqsucc':'\u22DF','curlyvee':'\u22CE','curlywedge':'\u22CF','curren':'\xA4','curvearrowleft':'\u21B6','curvearrowright':'\u21B7','cuvee':'\u22CE','cuwed':'\u22CF','cwconint':'\u2232','cwint':'\u2231','cylcty':'\u232D','dagger':'\u2020','Dagger':'\u2021','daleth':'\u2138','darr':'\u2193','dArr':'\u21D3','Darr':'\u21A1','dash':'\u2010','dashv':'\u22A3','Dashv':'\u2AE4','dbkarow':'\u290F','dblac':'\u02DD','dcaron':'\u010F','Dcaron':'\u010E','dcy':'\u0434','Dcy':'\u0414','dd':'\u2146','DD':'\u2145','ddagger':'\u2021','ddarr':'\u21CA','DDotrahd':'\u2911','ddotseq':'\u2A77','deg':'\xB0','Del':'\u2207','delta':'\u03B4','Delta':'\u0394','demptyv':'\u29B1','dfisht':'\u297F','dfr':'\uD835\uDD21','Dfr':'\uD835\uDD07','dHar':'\u2965','dharl':'\u21C3','dharr':'\u21C2','DiacriticalAcute':'\xB4','DiacriticalDot':'\u02D9','DiacriticalDoubleAcute':'\u02DD','DiacriticalGrave':'`','DiacriticalTilde':'\u02DC','diam':'\u22C4','diamond':'\u22C4','Diamond':'\u22C4','diamondsuit':'\u2666','diams':'\u2666','die':'\xA8','DifferentialD':'\u2146','digamma':'\u03DD','disin':'\u22F2','div':'\xF7','divide':'\xF7','divideontimes':'\u22C7','divonx':'\u22C7','djcy':'\u0452','DJcy':'\u0402','dlcorn':'\u231E','dlcrop':'\u230D','dollar':'$','dopf':'\uD835\uDD55','Dopf':'\uD835\uDD3B','dot':'\u02D9','Dot':'\xA8','DotDot':'\u20DC','doteq':'\u2250','doteqdot':'\u2251','DotEqual':'\u2250','dotminus':'\u2238','dotplus':'\u2214','dotsquare':'\u22A1','doublebarwedge':'\u2306','DoubleContourIntegral':'\u222F','DoubleDot':'\xA8','DoubleDownArrow':'\u21D3','DoubleLeftArrow':'\u21D0','DoubleLeftRightArrow':'\u21D4','DoubleLeftTee':'\u2AE4','DoubleLongLeftArrow':'\u27F8','DoubleLongLeftRightArrow':'\u27FA','DoubleLongRightArrow':'\u27F9','DoubleRightArrow':'\u21D2','DoubleRightTee':'\u22A8','DoubleUpArrow':'\u21D1','DoubleUpDownArrow':'\u21D5','DoubleVerticalBar':'\u2225','downarrow':'\u2193','Downarrow':'\u21D3','DownArrow':'\u2193','DownArrowBar':'\u2913','DownArrowUpArrow':'\u21F5','DownBreve':'\u0311','downdownarrows':'\u21CA','downharpoonleft':'\u21C3','downharpoonright':'\u21C2','DownLeftRightVector':'\u2950','DownLeftTeeVector':'\u295E','DownLeftVector':'\u21BD','DownLeftVectorBar':'\u2956','DownRightTeeVector':'\u295F','DownRightVector':'\u21C1','DownRightVectorBar':'\u2957','DownTee':'\u22A4','DownTeeArrow':'\u21A7','drbkarow':'\u2910','drcorn':'\u231F','drcrop':'\u230C','dscr':'\uD835\uDCB9','Dscr':'\uD835\uDC9F','dscy':'\u0455','DScy':'\u0405','dsol':'\u29F6','dstrok':'\u0111','Dstrok':'\u0110','dtdot':'\u22F1','dtri':'\u25BF','dtrif':'\u25BE','duarr':'\u21F5','duhar':'\u296F','dwangle':'\u29A6','dzcy':'\u045F','DZcy':'\u040F','dzigrarr':'\u27FF','eacute':'\xE9','Eacute':'\xC9','easter':'\u2A6E','ecaron':'\u011B','Ecaron':'\u011A','ecir':'\u2256','ecirc':'\xEA','Ecirc':'\xCA','ecolon':'\u2255','ecy':'\u044D','Ecy':'\u042D','eDDot':'\u2A77','edot':'\u0117','eDot':'\u2251','Edot':'\u0116','ee':'\u2147','efDot':'\u2252','efr':'\uD835\uDD22','Efr':'\uD835\uDD08','eg':'\u2A9A','egrave':'\xE8','Egrave':'\xC8','egs':'\u2A96','egsdot':'\u2A98','el':'\u2A99','Element':'\u2208','elinters':'\u23E7','ell':'\u2113','els':'\u2A95','elsdot':'\u2A97','emacr':'\u0113','Emacr':'\u0112','empty':'\u2205','emptyset':'\u2205','EmptySmallSquare':'\u25FB','emptyv':'\u2205','EmptyVerySmallSquare':'\u25AB','emsp':'\u2003','emsp13':'\u2004','emsp14':'\u2005','eng':'\u014B','ENG':'\u014A','ensp':'\u2002','eogon':'\u0119','Eogon':'\u0118','eopf':'\uD835\uDD56','Eopf':'\uD835\uDD3C','epar':'\u22D5','eparsl':'\u29E3','eplus':'\u2A71','epsi':'\u03B5','epsilon':'\u03B5','Epsilon':'\u0395','epsiv':'\u03F5','eqcirc':'\u2256','eqcolon':'\u2255','eqsim':'\u2242','eqslantgtr':'\u2A96','eqslantless':'\u2A95','Equal':'\u2A75','equals':'=','EqualTilde':'\u2242','equest':'\u225F','Equilibrium':'\u21CC','equiv':'\u2261','equivDD':'\u2A78','eqvparsl':'\u29E5','erarr':'\u2971','erDot':'\u2253','escr':'\u212F','Escr':'\u2130','esdot':'\u2250','esim':'\u2242','Esim':'\u2A73','eta':'\u03B7','Eta':'\u0397','eth':'\xF0','ETH':'\xD0','euml':'\xEB','Euml':'\xCB','euro':'\u20AC','excl':'!','exist':'\u2203','Exists':'\u2203','expectation':'\u2130','exponentiale':'\u2147','ExponentialE':'\u2147','fallingdotseq':'\u2252','fcy':'\u0444','Fcy':'\u0424','female':'\u2640','ffilig':'\uFB03','fflig':'\uFB00','ffllig':'\uFB04','ffr':'\uD835\uDD23','Ffr':'\uD835\uDD09','filig':'\uFB01','FilledSmallSquare':'\u25FC','FilledVerySmallSquare':'\u25AA','fjlig':'fj','flat':'\u266D','fllig':'\uFB02','fltns':'\u25B1','fnof':'\u0192','fopf':'\uD835\uDD57','Fopf':'\uD835\uDD3D','forall':'\u2200','ForAll':'\u2200','fork':'\u22D4','forkv':'\u2AD9','Fouriertrf':'\u2131','fpartint':'\u2A0D','frac12':'\xBD','frac13':'\u2153','frac14':'\xBC','frac15':'\u2155','frac16':'\u2159','frac18':'\u215B','frac23':'\u2154','frac25':'\u2156','frac34':'\xBE','frac35':'\u2157','frac38':'\u215C','frac45':'\u2158','frac56':'\u215A','frac58':'\u215D','frac78':'\u215E','frasl':'\u2044','frown':'\u2322','fscr':'\uD835\uDCBB','Fscr':'\u2131','gacute':'\u01F5','gamma':'\u03B3','Gamma':'\u0393','gammad':'\u03DD','Gammad':'\u03DC','gap':'\u2A86','gbreve':'\u011F','Gbreve':'\u011E','Gcedil':'\u0122','gcirc':'\u011D','Gcirc':'\u011C','gcy':'\u0433','Gcy':'\u0413','gdot':'\u0121','Gdot':'\u0120','ge':'\u2265','gE':'\u2267','gel':'\u22DB','gEl':'\u2A8C','geq':'\u2265','geqq':'\u2267','geqslant':'\u2A7E','ges':'\u2A7E','gescc':'\u2AA9','gesdot':'\u2A80','gesdoto':'\u2A82','gesdotol':'\u2A84','gesl':'\u22DB\uFE00','gesles':'\u2A94','gfr':'\uD835\uDD24','Gfr':'\uD835\uDD0A','gg':'\u226B','Gg':'\u22D9','ggg':'\u22D9','gimel':'\u2137','gjcy':'\u0453','GJcy':'\u0403','gl':'\u2277','gla':'\u2AA5','glE':'\u2A92','glj':'\u2AA4','gnap':'\u2A8A','gnapprox':'\u2A8A','gne':'\u2A88','gnE':'\u2269','gneq':'\u2A88','gneqq':'\u2269','gnsim':'\u22E7','gopf':'\uD835\uDD58','Gopf':'\uD835\uDD3E','grave':'`','GreaterEqual':'\u2265','GreaterEqualLess':'\u22DB','GreaterFullEqual':'\u2267','GreaterGreater':'\u2AA2','GreaterLess':'\u2277','GreaterSlantEqual':'\u2A7E','GreaterTilde':'\u2273','gscr':'\u210A','Gscr':'\uD835\uDCA2','gsim':'\u2273','gsime':'\u2A8E','gsiml':'\u2A90','gt':'>','Gt':'\u226B','GT':'>','gtcc':'\u2AA7','gtcir':'\u2A7A','gtdot':'\u22D7','gtlPar':'\u2995','gtquest':'\u2A7C','gtrapprox':'\u2A86','gtrarr':'\u2978','gtrdot':'\u22D7','gtreqless':'\u22DB','gtreqqless':'\u2A8C','gtrless':'\u2277','gtrsim':'\u2273','gvertneqq':'\u2269\uFE00','gvnE':'\u2269\uFE00','Hacek':'\u02C7','hairsp':'\u200A','half':'\xBD','hamilt':'\u210B','hardcy':'\u044A','HARDcy':'\u042A','harr':'\u2194','hArr':'\u21D4','harrcir':'\u2948','harrw':'\u21AD','Hat':'^','hbar':'\u210F','hcirc':'\u0125','Hcirc':'\u0124','hearts':'\u2665','heartsuit':'\u2665','hellip':'\u2026','hercon':'\u22B9','hfr':'\uD835\uDD25','Hfr':'\u210C','HilbertSpace':'\u210B','hksearow':'\u2925','hkswarow':'\u2926','hoarr':'\u21FF','homtht':'\u223B','hookleftarrow':'\u21A9','hookrightarrow':'\u21AA','hopf':'\uD835\uDD59','Hopf':'\u210D','horbar':'\u2015','HorizontalLine':'\u2500','hscr':'\uD835\uDCBD','Hscr':'\u210B','hslash':'\u210F','hstrok':'\u0127','Hstrok':'\u0126','HumpDownHump':'\u224E','HumpEqual':'\u224F','hybull':'\u2043','hyphen':'\u2010','iacute':'\xED','Iacute':'\xCD','ic':'\u2063','icirc':'\xEE','Icirc':'\xCE','icy':'\u0438','Icy':'\u0418','Idot':'\u0130','iecy':'\u0435','IEcy':'\u0415','iexcl':'\xA1','iff':'\u21D4','ifr':'\uD835\uDD26','Ifr':'\u2111','igrave':'\xEC','Igrave':'\xCC','ii':'\u2148','iiiint':'\u2A0C','iiint':'\u222D','iinfin':'\u29DC','iiota':'\u2129','ijlig':'\u0133','IJlig':'\u0132','Im':'\u2111','imacr':'\u012B','Imacr':'\u012A','image':'\u2111','ImaginaryI':'\u2148','imagline':'\u2110','imagpart':'\u2111','imath':'\u0131','imof':'\u22B7','imped':'\u01B5','Implies':'\u21D2','in':'\u2208','incare':'\u2105','infin':'\u221E','infintie':'\u29DD','inodot':'\u0131','int':'\u222B','Int':'\u222C','intcal':'\u22BA','integers':'\u2124','Integral':'\u222B','intercal':'\u22BA','Intersection':'\u22C2','intlarhk':'\u2A17','intprod':'\u2A3C','InvisibleComma':'\u2063','InvisibleTimes':'\u2062','iocy':'\u0451','IOcy':'\u0401','iogon':'\u012F','Iogon':'\u012E','iopf':'\uD835\uDD5A','Iopf':'\uD835\uDD40','iota':'\u03B9','Iota':'\u0399','iprod':'\u2A3C','iquest':'\xBF','iscr':'\uD835\uDCBE','Iscr':'\u2110','isin':'\u2208','isindot':'\u22F5','isinE':'\u22F9','isins':'\u22F4','isinsv':'\u22F3','isinv':'\u2208','it':'\u2062','itilde':'\u0129','Itilde':'\u0128','iukcy':'\u0456','Iukcy':'\u0406','iuml':'\xEF','Iuml':'\xCF','jcirc':'\u0135','Jcirc':'\u0134','jcy':'\u0439','Jcy':'\u0419','jfr':'\uD835\uDD27','Jfr':'\uD835\uDD0D','jmath':'\u0237','jopf':'\uD835\uDD5B','Jopf':'\uD835\uDD41','jscr':'\uD835\uDCBF','Jscr':'\uD835\uDCA5','jsercy':'\u0458','Jsercy':'\u0408','jukcy':'\u0454','Jukcy':'\u0404','kappa':'\u03BA','Kappa':'\u039A','kappav':'\u03F0','kcedil':'\u0137','Kcedil':'\u0136','kcy':'\u043A','Kcy':'\u041A','kfr':'\uD835\uDD28','Kfr':'\uD835\uDD0E','kgreen':'\u0138','khcy':'\u0445','KHcy':'\u0425','kjcy':'\u045C','KJcy':'\u040C','kopf':'\uD835\uDD5C','Kopf':'\uD835\uDD42','kscr':'\uD835\uDCC0','Kscr':'\uD835\uDCA6','lAarr':'\u21DA','lacute':'\u013A','Lacute':'\u0139','laemptyv':'\u29B4','lagran':'\u2112','lambda':'\u03BB','Lambda':'\u039B','lang':'\u27E8','Lang':'\u27EA','langd':'\u2991','langle':'\u27E8','lap':'\u2A85','Laplacetrf':'\u2112','laquo':'\xAB','larr':'\u2190','lArr':'\u21D0','Larr':'\u219E','larrb':'\u21E4','larrbfs':'\u291F','larrfs':'\u291D','larrhk':'\u21A9','larrlp':'\u21AB','larrpl':'\u2939','larrsim':'\u2973','larrtl':'\u21A2','lat':'\u2AAB','latail':'\u2919','lAtail':'\u291B','late':'\u2AAD','lates':'\u2AAD\uFE00','lbarr':'\u290C','lBarr':'\u290E','lbbrk':'\u2772','lbrace':'{','lbrack':'[','lbrke':'\u298B','lbrksld':'\u298F','lbrkslu':'\u298D','lcaron':'\u013E','Lcaron':'\u013D','lcedil':'\u013C','Lcedil':'\u013B','lceil':'\u2308','lcub':'{','lcy':'\u043B','Lcy':'\u041B','ldca':'\u2936','ldquo':'\u201C','ldquor':'\u201E','ldrdhar':'\u2967','ldrushar':'\u294B','ldsh':'\u21B2','le':'\u2264','lE':'\u2266','LeftAngleBracket':'\u27E8','leftarrow':'\u2190','Leftarrow':'\u21D0','LeftArrow':'\u2190','LeftArrowBar':'\u21E4','LeftArrowRightArrow':'\u21C6','leftarrowtail':'\u21A2','LeftCeiling':'\u2308','LeftDoubleBracket':'\u27E6','LeftDownTeeVector':'\u2961','LeftDownVector':'\u21C3','LeftDownVectorBar':'\u2959','LeftFloor':'\u230A','leftharpoondown':'\u21BD','leftharpoonup':'\u21BC','leftleftarrows':'\u21C7','leftrightarrow':'\u2194','Leftrightarrow':'\u21D4','LeftRightArrow':'\u2194','leftrightarrows':'\u21C6','leftrightharpoons':'\u21CB','leftrightsquigarrow':'\u21AD','LeftRightVector':'\u294E','LeftTee':'\u22A3','LeftTeeArrow':'\u21A4','LeftTeeVector':'\u295A','leftthreetimes':'\u22CB','LeftTriangle':'\u22B2','LeftTriangleBar':'\u29CF','LeftTriangleEqual':'\u22B4','LeftUpDownVector':'\u2951','LeftUpTeeVector':'\u2960','LeftUpVector':'\u21BF','LeftUpVectorBar':'\u2958','LeftVector':'\u21BC','LeftVectorBar':'\u2952','leg':'\u22DA','lEg':'\u2A8B','leq':'\u2264','leqq':'\u2266','leqslant':'\u2A7D','les':'\u2A7D','lescc':'\u2AA8','lesdot':'\u2A7F','lesdoto':'\u2A81','lesdotor':'\u2A83','lesg':'\u22DA\uFE00','lesges':'\u2A93','lessapprox':'\u2A85','lessdot':'\u22D6','lesseqgtr':'\u22DA','lesseqqgtr':'\u2A8B','LessEqualGreater':'\u22DA','LessFullEqual':'\u2266','LessGreater':'\u2276','lessgtr':'\u2276','LessLess':'\u2AA1','lesssim':'\u2272','LessSlantEqual':'\u2A7D','LessTilde':'\u2272','lfisht':'\u297C','lfloor':'\u230A','lfr':'\uD835\uDD29','Lfr':'\uD835\uDD0F','lg':'\u2276','lgE':'\u2A91','lHar':'\u2962','lhard':'\u21BD','lharu':'\u21BC','lharul':'\u296A','lhblk':'\u2584','ljcy':'\u0459','LJcy':'\u0409','ll':'\u226A','Ll':'\u22D8','llarr':'\u21C7','llcorner':'\u231E','Lleftarrow':'\u21DA','llhard':'\u296B','lltri':'\u25FA','lmidot':'\u0140','Lmidot':'\u013F','lmoust':'\u23B0','lmoustache':'\u23B0','lnap':'\u2A89','lnapprox':'\u2A89','lne':'\u2A87','lnE':'\u2268','lneq':'\u2A87','lneqq':'\u2268','lnsim':'\u22E6','loang':'\u27EC','loarr':'\u21FD','lobrk':'\u27E6','longleftarrow':'\u27F5','Longleftarrow':'\u27F8','LongLeftArrow':'\u27F5','longleftrightarrow':'\u27F7','Longleftrightarrow':'\u27FA','LongLeftRightArrow':'\u27F7','longmapsto':'\u27FC','longrightarrow':'\u27F6','Longrightarrow':'\u27F9','LongRightArrow':'\u27F6','looparrowleft':'\u21AB','looparrowright':'\u21AC','lopar':'\u2985','lopf':'\uD835\uDD5D','Lopf':'\uD835\uDD43','loplus':'\u2A2D','lotimes':'\u2A34','lowast':'\u2217','lowbar':'_','LowerLeftArrow':'\u2199','LowerRightArrow':'\u2198','loz':'\u25CA','lozenge':'\u25CA','lozf':'\u29EB','lpar':'(','lparlt':'\u2993','lrarr':'\u21C6','lrcorner':'\u231F','lrhar':'\u21CB','lrhard':'\u296D','lrm':'\u200E','lrtri':'\u22BF','lsaquo':'\u2039','lscr':'\uD835\uDCC1','Lscr':'\u2112','lsh':'\u21B0','Lsh':'\u21B0','lsim':'\u2272','lsime':'\u2A8D','lsimg':'\u2A8F','lsqb':'[','lsquo':'\u2018','lsquor':'\u201A','lstrok':'\u0142','Lstrok':'\u0141','lt':'<','Lt':'\u226A','LT':'<','ltcc':'\u2AA6','ltcir':'\u2A79','ltdot':'\u22D6','lthree':'\u22CB','ltimes':'\u22C9','ltlarr':'\u2976','ltquest':'\u2A7B','ltri':'\u25C3','ltrie':'\u22B4','ltrif':'\u25C2','ltrPar':'\u2996','lurdshar':'\u294A','luruhar':'\u2966','lvertneqq':'\u2268\uFE00','lvnE':'\u2268\uFE00','macr':'\xAF','male':'\u2642','malt':'\u2720','maltese':'\u2720','map':'\u21A6','Map':'\u2905','mapsto':'\u21A6','mapstodown':'\u21A7','mapstoleft':'\u21A4','mapstoup':'\u21A5','marker':'\u25AE','mcomma':'\u2A29','mcy':'\u043C','Mcy':'\u041C','mdash':'\u2014','mDDot':'\u223A','measuredangle':'\u2221','MediumSpace':'\u205F','Mellintrf':'\u2133','mfr':'\uD835\uDD2A','Mfr':'\uD835\uDD10','mho':'\u2127','micro':'\xB5','mid':'\u2223','midast':'*','midcir':'\u2AF0','middot':'\xB7','minus':'\u2212','minusb':'\u229F','minusd':'\u2238','minusdu':'\u2A2A','MinusPlus':'\u2213','mlcp':'\u2ADB','mldr':'\u2026','mnplus':'\u2213','models':'\u22A7','mopf':'\uD835\uDD5E','Mopf':'\uD835\uDD44','mp':'\u2213','mscr':'\uD835\uDCC2','Mscr':'\u2133','mstpos':'\u223E','mu':'\u03BC','Mu':'\u039C','multimap':'\u22B8','mumap':'\u22B8','nabla':'\u2207','nacute':'\u0144','Nacute':'\u0143','nang':'\u2220\u20D2','nap':'\u2249','napE':'\u2A70\u0338','napid':'\u224B\u0338','napos':'\u0149','napprox':'\u2249','natur':'\u266E','natural':'\u266E','naturals':'\u2115','nbsp':'\xA0','nbump':'\u224E\u0338','nbumpe':'\u224F\u0338','ncap':'\u2A43','ncaron':'\u0148','Ncaron':'\u0147','ncedil':'\u0146','Ncedil':'\u0145','ncong':'\u2247','ncongdot':'\u2A6D\u0338','ncup':'\u2A42','ncy':'\u043D','Ncy':'\u041D','ndash':'\u2013','ne':'\u2260','nearhk':'\u2924','nearr':'\u2197','neArr':'\u21D7','nearrow':'\u2197','nedot':'\u2250\u0338','NegativeMediumSpace':'\u200B','NegativeThickSpace':'\u200B','NegativeThinSpace':'\u200B','NegativeVeryThinSpace':'\u200B','nequiv':'\u2262','nesear':'\u2928','nesim':'\u2242\u0338','NestedGreaterGreater':'\u226B','NestedLessLess':'\u226A','NewLine':'\n','nexist':'\u2204','nexists':'\u2204','nfr':'\uD835\uDD2B','Nfr':'\uD835\uDD11','nge':'\u2271','ngE':'\u2267\u0338','ngeq':'\u2271','ngeqq':'\u2267\u0338','ngeqslant':'\u2A7E\u0338','nges':'\u2A7E\u0338','nGg':'\u22D9\u0338','ngsim':'\u2275','ngt':'\u226F','nGt':'\u226B\u20D2','ngtr':'\u226F','nGtv':'\u226B\u0338','nharr':'\u21AE','nhArr':'\u21CE','nhpar':'\u2AF2','ni':'\u220B','nis':'\u22FC','nisd':'\u22FA','niv':'\u220B','njcy':'\u045A','NJcy':'\u040A','nlarr':'\u219A','nlArr':'\u21CD','nldr':'\u2025','nle':'\u2270','nlE':'\u2266\u0338','nleftarrow':'\u219A','nLeftarrow':'\u21CD','nleftrightarrow':'\u21AE','nLeftrightarrow':'\u21CE','nleq':'\u2270','nleqq':'\u2266\u0338','nleqslant':'\u2A7D\u0338','nles':'\u2A7D\u0338','nless':'\u226E','nLl':'\u22D8\u0338','nlsim':'\u2274','nlt':'\u226E','nLt':'\u226A\u20D2','nltri':'\u22EA','nltrie':'\u22EC','nLtv':'\u226A\u0338','nmid':'\u2224','NoBreak':'\u2060','NonBreakingSpace':'\xA0','nopf':'\uD835\uDD5F','Nopf':'\u2115','not':'\xAC','Not':'\u2AEC','NotCongruent':'\u2262','NotCupCap':'\u226D','NotDoubleVerticalBar':'\u2226','NotElement':'\u2209','NotEqual':'\u2260','NotEqualTilde':'\u2242\u0338','NotExists':'\u2204','NotGreater':'\u226F','NotGreaterEqual':'\u2271','NotGreaterFullEqual':'\u2267\u0338','NotGreaterGreater':'\u226B\u0338','NotGreaterLess':'\u2279','NotGreaterSlantEqual':'\u2A7E\u0338','NotGreaterTilde':'\u2275','NotHumpDownHump':'\u224E\u0338','NotHumpEqual':'\u224F\u0338','notin':'\u2209','notindot':'\u22F5\u0338','notinE':'\u22F9\u0338','notinva':'\u2209','notinvb':'\u22F7','notinvc':'\u22F6','NotLeftTriangle':'\u22EA','NotLeftTriangleBar':'\u29CF\u0338','NotLeftTriangleEqual':'\u22EC','NotLess':'\u226E','NotLessEqual':'\u2270','NotLessGreater':'\u2278','NotLessLess':'\u226A\u0338','NotLessSlantEqual':'\u2A7D\u0338','NotLessTilde':'\u2274','NotNestedGreaterGreater':'\u2AA2\u0338','NotNestedLessLess':'\u2AA1\u0338','notni':'\u220C','notniva':'\u220C','notnivb':'\u22FE','notnivc':'\u22FD','NotPrecedes':'\u2280','NotPrecedesEqual':'\u2AAF\u0338','NotPrecedesSlantEqual':'\u22E0','NotReverseElement':'\u220C','NotRightTriangle':'\u22EB','NotRightTriangleBar':'\u29D0\u0338','NotRightTriangleEqual':'\u22ED','NotSquareSubset':'\u228F\u0338','NotSquareSubsetEqual':'\u22E2','NotSquareSuperset':'\u2290\u0338','NotSquareSupersetEqual':'\u22E3','NotSubset':'\u2282\u20D2','NotSubsetEqual':'\u2288','NotSucceeds':'\u2281','NotSucceedsEqual':'\u2AB0\u0338','NotSucceedsSlantEqual':'\u22E1','NotSucceedsTilde':'\u227F\u0338','NotSuperset':'\u2283\u20D2','NotSupersetEqual':'\u2289','NotTilde':'\u2241','NotTildeEqual':'\u2244','NotTildeFullEqual':'\u2247','NotTildeTilde':'\u2249','NotVerticalBar':'\u2224','npar':'\u2226','nparallel':'\u2226','nparsl':'\u2AFD\u20E5','npart':'\u2202\u0338','npolint':'\u2A14','npr':'\u2280','nprcue':'\u22E0','npre':'\u2AAF\u0338','nprec':'\u2280','npreceq':'\u2AAF\u0338','nrarr':'\u219B','nrArr':'\u21CF','nrarrc':'\u2933\u0338','nrarrw':'\u219D\u0338','nrightarrow':'\u219B','nRightarrow':'\u21CF','nrtri':'\u22EB','nrtrie':'\u22ED','nsc':'\u2281','nsccue':'\u22E1','nsce':'\u2AB0\u0338','nscr':'\uD835\uDCC3','Nscr':'\uD835\uDCA9','nshortmid':'\u2224','nshortparallel':'\u2226','nsim':'\u2241','nsime':'\u2244','nsimeq':'\u2244','nsmid':'\u2224','nspar':'\u2226','nsqsube':'\u22E2','nsqsupe':'\u22E3','nsub':'\u2284','nsube':'\u2288','nsubE':'\u2AC5\u0338','nsubset':'\u2282\u20D2','nsubseteq':'\u2288','nsubseteqq':'\u2AC5\u0338','nsucc':'\u2281','nsucceq':'\u2AB0\u0338','nsup':'\u2285','nsupe':'\u2289','nsupE':'\u2AC6\u0338','nsupset':'\u2283\u20D2','nsupseteq':'\u2289','nsupseteqq':'\u2AC6\u0338','ntgl':'\u2279','ntilde':'\xF1','Ntilde':'\xD1','ntlg':'\u2278','ntriangleleft':'\u22EA','ntrianglelefteq':'\u22EC','ntriangleright':'\u22EB','ntrianglerighteq':'\u22ED','nu':'\u03BD','Nu':'\u039D','num':'#','numero':'\u2116','numsp':'\u2007','nvap':'\u224D\u20D2','nvdash':'\u22AC','nvDash':'\u22AD','nVdash':'\u22AE','nVDash':'\u22AF','nvge':'\u2265\u20D2','nvgt':'>\u20D2','nvHarr':'\u2904','nvinfin':'\u29DE','nvlArr':'\u2902','nvle':'\u2264\u20D2','nvlt':'<\u20D2','nvltrie':'\u22B4\u20D2','nvrArr':'\u2903','nvrtrie':'\u22B5\u20D2','nvsim':'\u223C\u20D2','nwarhk':'\u2923','nwarr':'\u2196','nwArr':'\u21D6','nwarrow':'\u2196','nwnear':'\u2927','oacute':'\xF3','Oacute':'\xD3','oast':'\u229B','ocir':'\u229A','ocirc':'\xF4','Ocirc':'\xD4','ocy':'\u043E','Ocy':'\u041E','odash':'\u229D','odblac':'\u0151','Odblac':'\u0150','odiv':'\u2A38','odot':'\u2299','odsold':'\u29BC','oelig':'\u0153','OElig':'\u0152','ofcir':'\u29BF','ofr':'\uD835\uDD2C','Ofr':'\uD835\uDD12','ogon':'\u02DB','ograve':'\xF2','Ograve':'\xD2','ogt':'\u29C1','ohbar':'\u29B5','ohm':'\u03A9','oint':'\u222E','olarr':'\u21BA','olcir':'\u29BE','olcross':'\u29BB','oline':'\u203E','olt':'\u29C0','omacr':'\u014D','Omacr':'\u014C','omega':'\u03C9','Omega':'\u03A9','omicron':'\u03BF','Omicron':'\u039F','omid':'\u29B6','ominus':'\u2296','oopf':'\uD835\uDD60','Oopf':'\uD835\uDD46','opar':'\u29B7','OpenCurlyDoubleQuote':'\u201C','OpenCurlyQuote':'\u2018','operp':'\u29B9','oplus':'\u2295','or':'\u2228','Or':'\u2A54','orarr':'\u21BB','ord':'\u2A5D','order':'\u2134','orderof':'\u2134','ordf':'\xAA','ordm':'\xBA','origof':'\u22B6','oror':'\u2A56','orslope':'\u2A57','orv':'\u2A5B','oS':'\u24C8','oscr':'\u2134','Oscr':'\uD835\uDCAA','oslash':'\xF8','Oslash':'\xD8','osol':'\u2298','otilde':'\xF5','Otilde':'\xD5','otimes':'\u2297','Otimes':'\u2A37','otimesas':'\u2A36','ouml':'\xF6','Ouml':'\xD6','ovbar':'\u233D','OverBar':'\u203E','OverBrace':'\u23DE','OverBracket':'\u23B4','OverParenthesis':'\u23DC','par':'\u2225','para':'\xB6','parallel':'\u2225','parsim':'\u2AF3','parsl':'\u2AFD','part':'\u2202','PartialD':'\u2202','pcy':'\u043F','Pcy':'\u041F','percnt':'%','period':'.','permil':'\u2030','perp':'\u22A5','pertenk':'\u2031','pfr':'\uD835\uDD2D','Pfr':'\uD835\uDD13','phi':'\u03C6','Phi':'\u03A6','phiv':'\u03D5','phmmat':'\u2133','phone':'\u260E','pi':'\u03C0','Pi':'\u03A0','pitchfork':'\u22D4','piv':'\u03D6','planck':'\u210F','planckh':'\u210E','plankv':'\u210F','plus':'+','plusacir':'\u2A23','plusb':'\u229E','pluscir':'\u2A22','plusdo':'\u2214','plusdu':'\u2A25','pluse':'\u2A72','PlusMinus':'\xB1','plusmn':'\xB1','plussim':'\u2A26','plustwo':'\u2A27','pm':'\xB1','Poincareplane':'\u210C','pointint':'\u2A15','popf':'\uD835\uDD61','Popf':'\u2119','pound':'\xA3','pr':'\u227A','Pr':'\u2ABB','prap':'\u2AB7','prcue':'\u227C','pre':'\u2AAF','prE':'\u2AB3','prec':'\u227A','precapprox':'\u2AB7','preccurlyeq':'\u227C','Precedes':'\u227A','PrecedesEqual':'\u2AAF','PrecedesSlantEqual':'\u227C','PrecedesTilde':'\u227E','preceq':'\u2AAF','precnapprox':'\u2AB9','precneqq':'\u2AB5','precnsim':'\u22E8','precsim':'\u227E','prime':'\u2032','Prime':'\u2033','primes':'\u2119','prnap':'\u2AB9','prnE':'\u2AB5','prnsim':'\u22E8','prod':'\u220F','Product':'\u220F','profalar':'\u232E','profline':'\u2312','profsurf':'\u2313','prop':'\u221D','Proportion':'\u2237','Proportional':'\u221D','propto':'\u221D','prsim':'\u227E','prurel':'\u22B0','pscr':'\uD835\uDCC5','Pscr':'\uD835\uDCAB','psi':'\u03C8','Psi':'\u03A8','puncsp':'\u2008','qfr':'\uD835\uDD2E','Qfr':'\uD835\uDD14','qint':'\u2A0C','qopf':'\uD835\uDD62','Qopf':'\u211A','qprime':'\u2057','qscr':'\uD835\uDCC6','Qscr':'\uD835\uDCAC','quaternions':'\u210D','quatint':'\u2A16','quest':'?','questeq':'\u225F','quot':'"','QUOT':'"','rAarr':'\u21DB','race':'\u223D\u0331','racute':'\u0155','Racute':'\u0154','radic':'\u221A','raemptyv':'\u29B3','rang':'\u27E9','Rang':'\u27EB','rangd':'\u2992','range':'\u29A5','rangle':'\u27E9','raquo':'\xBB','rarr':'\u2192','rArr':'\u21D2','Rarr':'\u21A0','rarrap':'\u2975','rarrb':'\u21E5','rarrbfs':'\u2920','rarrc':'\u2933','rarrfs':'\u291E','rarrhk':'\u21AA','rarrlp':'\u21AC','rarrpl':'\u2945','rarrsim':'\u2974','rarrtl':'\u21A3','Rarrtl':'\u2916','rarrw':'\u219D','ratail':'\u291A','rAtail':'\u291C','ratio':'\u2236','rationals':'\u211A','rbarr':'\u290D','rBarr':'\u290F','RBarr':'\u2910','rbbrk':'\u2773','rbrace':'}','rbrack':']','rbrke':'\u298C','rbrksld':'\u298E','rbrkslu':'\u2990','rcaron':'\u0159','Rcaron':'\u0158','rcedil':'\u0157','Rcedil':'\u0156','rceil':'\u2309','rcub':'}','rcy':'\u0440','Rcy':'\u0420','rdca':'\u2937','rdldhar':'\u2969','rdquo':'\u201D','rdquor':'\u201D','rdsh':'\u21B3','Re':'\u211C','real':'\u211C','realine':'\u211B','realpart':'\u211C','reals':'\u211D','rect':'\u25AD','reg':'\xAE','REG':'\xAE','ReverseElement':'\u220B','ReverseEquilibrium':'\u21CB','ReverseUpEquilibrium':'\u296F','rfisht':'\u297D','rfloor':'\u230B','rfr':'\uD835\uDD2F','Rfr':'\u211C','rHar':'\u2964','rhard':'\u21C1','rharu':'\u21C0','rharul':'\u296C','rho':'\u03C1','Rho':'\u03A1','rhov':'\u03F1','RightAngleBracket':'\u27E9','rightarrow':'\u2192','Rightarrow':'\u21D2','RightArrow':'\u2192','RightArrowBar':'\u21E5','RightArrowLeftArrow':'\u21C4','rightarrowtail':'\u21A3','RightCeiling':'\u2309','RightDoubleBracket':'\u27E7','RightDownTeeVector':'\u295D','RightDownVector':'\u21C2','RightDownVectorBar':'\u2955','RightFloor':'\u230B','rightharpoondown':'\u21C1','rightharpoonup':'\u21C0','rightleftarrows':'\u21C4','rightleftharpoons':'\u21CC','rightrightarrows':'\u21C9','rightsquigarrow':'\u219D','RightTee':'\u22A2','RightTeeArrow':'\u21A6','RightTeeVector':'\u295B','rightthreetimes':'\u22CC','RightTriangle':'\u22B3','RightTriangleBar':'\u29D0','RightTriangleEqual':'\u22B5','RightUpDownVector':'\u294F','RightUpTeeVector':'\u295C','RightUpVector':'\u21BE','RightUpVectorBar':'\u2954','RightVector':'\u21C0','RightVectorBar':'\u2953','ring':'\u02DA','risingdotseq':'\u2253','rlarr':'\u21C4','rlhar':'\u21CC','rlm':'\u200F','rmoust':'\u23B1','rmoustache':'\u23B1','rnmid':'\u2AEE','roang':'\u27ED','roarr':'\u21FE','robrk':'\u27E7','ropar':'\u2986','ropf':'\uD835\uDD63','Ropf':'\u211D','roplus':'\u2A2E','rotimes':'\u2A35','RoundImplies':'\u2970','rpar':')','rpargt':'\u2994','rppolint':'\u2A12','rrarr':'\u21C9','Rrightarrow':'\u21DB','rsaquo':'\u203A','rscr':'\uD835\uDCC7','Rscr':'\u211B','rsh':'\u21B1','Rsh':'\u21B1','rsqb':']','rsquo':'\u2019','rsquor':'\u2019','rthree':'\u22CC','rtimes':'\u22CA','rtri':'\u25B9','rtrie':'\u22B5','rtrif':'\u25B8','rtriltri':'\u29CE','RuleDelayed':'\u29F4','ruluhar':'\u2968','rx':'\u211E','sacute':'\u015B','Sacute':'\u015A','sbquo':'\u201A','sc':'\u227B','Sc':'\u2ABC','scap':'\u2AB8','scaron':'\u0161','Scaron':'\u0160','sccue':'\u227D','sce':'\u2AB0','scE':'\u2AB4','scedil':'\u015F','Scedil':'\u015E','scirc':'\u015D','Scirc':'\u015C','scnap':'\u2ABA','scnE':'\u2AB6','scnsim':'\u22E9','scpolint':'\u2A13','scsim':'\u227F','scy':'\u0441','Scy':'\u0421','sdot':'\u22C5','sdotb':'\u22A1','sdote':'\u2A66','searhk':'\u2925','searr':'\u2198','seArr':'\u21D8','searrow':'\u2198','sect':'\xA7','semi':';','seswar':'\u2929','setminus':'\u2216','setmn':'\u2216','sext':'\u2736','sfr':'\uD835\uDD30','Sfr':'\uD835\uDD16','sfrown':'\u2322','sharp':'\u266F','shchcy':'\u0449','SHCHcy':'\u0429','shcy':'\u0448','SHcy':'\u0428','ShortDownArrow':'\u2193','ShortLeftArrow':'\u2190','shortmid':'\u2223','shortparallel':'\u2225','ShortRightArrow':'\u2192','ShortUpArrow':'\u2191','shy':'\xAD','sigma':'\u03C3','Sigma':'\u03A3','sigmaf':'\u03C2','sigmav':'\u03C2','sim':'\u223C','simdot':'\u2A6A','sime':'\u2243','simeq':'\u2243','simg':'\u2A9E','simgE':'\u2AA0','siml':'\u2A9D','simlE':'\u2A9F','simne':'\u2246','simplus':'\u2A24','simrarr':'\u2972','slarr':'\u2190','SmallCircle':'\u2218','smallsetminus':'\u2216','smashp':'\u2A33','smeparsl':'\u29E4','smid':'\u2223','smile':'\u2323','smt':'\u2AAA','smte':'\u2AAC','smtes':'\u2AAC\uFE00','softcy':'\u044C','SOFTcy':'\u042C','sol':'/','solb':'\u29C4','solbar':'\u233F','sopf':'\uD835\uDD64','Sopf':'\uD835\uDD4A','spades':'\u2660','spadesuit':'\u2660','spar':'\u2225','sqcap':'\u2293','sqcaps':'\u2293\uFE00','sqcup':'\u2294','sqcups':'\u2294\uFE00','Sqrt':'\u221A','sqsub':'\u228F','sqsube':'\u2291','sqsubset':'\u228F','sqsubseteq':'\u2291','sqsup':'\u2290','sqsupe':'\u2292','sqsupset':'\u2290','sqsupseteq':'\u2292','squ':'\u25A1','square':'\u25A1','Square':'\u25A1','SquareIntersection':'\u2293','SquareSubset':'\u228F','SquareSubsetEqual':'\u2291','SquareSuperset':'\u2290','SquareSupersetEqual':'\u2292','SquareUnion':'\u2294','squarf':'\u25AA','squf':'\u25AA','srarr':'\u2192','sscr':'\uD835\uDCC8','Sscr':'\uD835\uDCAE','ssetmn':'\u2216','ssmile':'\u2323','sstarf':'\u22C6','star':'\u2606','Star':'\u22C6','starf':'\u2605','straightepsilon':'\u03F5','straightphi':'\u03D5','strns':'\xAF','sub':'\u2282','Sub':'\u22D0','subdot':'\u2ABD','sube':'\u2286','subE':'\u2AC5','subedot':'\u2AC3','submult':'\u2AC1','subne':'\u228A','subnE':'\u2ACB','subplus':'\u2ABF','subrarr':'\u2979','subset':'\u2282','Subset':'\u22D0','subseteq':'\u2286','subseteqq':'\u2AC5','SubsetEqual':'\u2286','subsetneq':'\u228A','subsetneqq':'\u2ACB','subsim':'\u2AC7','subsub':'\u2AD5','subsup':'\u2AD3','succ':'\u227B','succapprox':'\u2AB8','succcurlyeq':'\u227D','Succeeds':'\u227B','SucceedsEqual':'\u2AB0','SucceedsSlantEqual':'\u227D','SucceedsTilde':'\u227F','succeq':'\u2AB0','succnapprox':'\u2ABA','succneqq':'\u2AB6','succnsim':'\u22E9','succsim':'\u227F','SuchThat':'\u220B','sum':'\u2211','Sum':'\u2211','sung':'\u266A','sup':'\u2283','Sup':'\u22D1','sup1':'\xB9','sup2':'\xB2','sup3':'\xB3','supdot':'\u2ABE','supdsub':'\u2AD8','supe':'\u2287','supE':'\u2AC6','supedot':'\u2AC4','Superset':'\u2283','SupersetEqual':'\u2287','suphsol':'\u27C9','suphsub':'\u2AD7','suplarr':'\u297B','supmult':'\u2AC2','supne':'\u228B','supnE':'\u2ACC','supplus':'\u2AC0','supset':'\u2283','Supset':'\u22D1','supseteq':'\u2287','supseteqq':'\u2AC6','supsetneq':'\u228B','supsetneqq':'\u2ACC','supsim':'\u2AC8','supsub':'\u2AD4','supsup':'\u2AD6','swarhk':'\u2926','swarr':'\u2199','swArr':'\u21D9','swarrow':'\u2199','swnwar':'\u292A','szlig':'\xDF','Tab':'\t','target':'\u2316','tau':'\u03C4','Tau':'\u03A4','tbrk':'\u23B4','tcaron':'\u0165','Tcaron':'\u0164','tcedil':'\u0163','Tcedil':'\u0162','tcy':'\u0442','Tcy':'\u0422','tdot':'\u20DB','telrec':'\u2315','tfr':'\uD835\uDD31','Tfr':'\uD835\uDD17','there4':'\u2234','therefore':'\u2234','Therefore':'\u2234','theta':'\u03B8','Theta':'\u0398','thetasym':'\u03D1','thetav':'\u03D1','thickapprox':'\u2248','thicksim':'\u223C','ThickSpace':'\u205F\u200A','thinsp':'\u2009','ThinSpace':'\u2009','thkap':'\u2248','thksim':'\u223C','thorn':'\xFE','THORN':'\xDE','tilde':'\u02DC','Tilde':'\u223C','TildeEqual':'\u2243','TildeFullEqual':'\u2245','TildeTilde':'\u2248','times':'\xD7','timesb':'\u22A0','timesbar':'\u2A31','timesd':'\u2A30','tint':'\u222D','toea':'\u2928','top':'\u22A4','topbot':'\u2336','topcir':'\u2AF1','topf':'\uD835\uDD65','Topf':'\uD835\uDD4B','topfork':'\u2ADA','tosa':'\u2929','tprime':'\u2034','trade':'\u2122','TRADE':'\u2122','triangle':'\u25B5','triangledown':'\u25BF','triangleleft':'\u25C3','trianglelefteq':'\u22B4','triangleq':'\u225C','triangleright':'\u25B9','trianglerighteq':'\u22B5','tridot':'\u25EC','trie':'\u225C','triminus':'\u2A3A','TripleDot':'\u20DB','triplus':'\u2A39','trisb':'\u29CD','tritime':'\u2A3B','trpezium':'\u23E2','tscr':'\uD835\uDCC9','Tscr':'\uD835\uDCAF','tscy':'\u0446','TScy':'\u0426','tshcy':'\u045B','TSHcy':'\u040B','tstrok':'\u0167','Tstrok':'\u0166','twixt':'\u226C','twoheadleftarrow':'\u219E','twoheadrightarrow':'\u21A0','uacute':'\xFA','Uacute':'\xDA','uarr':'\u2191','uArr':'\u21D1','Uarr':'\u219F','Uarrocir':'\u2949','ubrcy':'\u045E','Ubrcy':'\u040E','ubreve':'\u016D','Ubreve':'\u016C','ucirc':'\xFB','Ucirc':'\xDB','ucy':'\u0443','Ucy':'\u0423','udarr':'\u21C5','udblac':'\u0171','Udblac':'\u0170','udhar':'\u296E','ufisht':'\u297E','ufr':'\uD835\uDD32','Ufr':'\uD835\uDD18','ugrave':'\xF9','Ugrave':'\xD9','uHar':'\u2963','uharl':'\u21BF','uharr':'\u21BE','uhblk':'\u2580','ulcorn':'\u231C','ulcorner':'\u231C','ulcrop':'\u230F','ultri':'\u25F8','umacr':'\u016B','Umacr':'\u016A','uml':'\xA8','UnderBar':'_','UnderBrace':'\u23DF','UnderBracket':'\u23B5','UnderParenthesis':'\u23DD','Union':'\u22C3','UnionPlus':'\u228E','uogon':'\u0173','Uogon':'\u0172','uopf':'\uD835\uDD66','Uopf':'\uD835\uDD4C','uparrow':'\u2191','Uparrow':'\u21D1','UpArrow':'\u2191','UpArrowBar':'\u2912','UpArrowDownArrow':'\u21C5','updownarrow':'\u2195','Updownarrow':'\u21D5','UpDownArrow':'\u2195','UpEquilibrium':'\u296E','upharpoonleft':'\u21BF','upharpoonright':'\u21BE','uplus':'\u228E','UpperLeftArrow':'\u2196','UpperRightArrow':'\u2197','upsi':'\u03C5','Upsi':'\u03D2','upsih':'\u03D2','upsilon':'\u03C5','Upsilon':'\u03A5','UpTee':'\u22A5','UpTeeArrow':'\u21A5','upuparrows':'\u21C8','urcorn':'\u231D','urcorner':'\u231D','urcrop':'\u230E','uring':'\u016F','Uring':'\u016E','urtri':'\u25F9','uscr':'\uD835\uDCCA','Uscr':'\uD835\uDCB0','utdot':'\u22F0','utilde':'\u0169','Utilde':'\u0168','utri':'\u25B5','utrif':'\u25B4','uuarr':'\u21C8','uuml':'\xFC','Uuml':'\xDC','uwangle':'\u29A7','vangrt':'\u299C','varepsilon':'\u03F5','varkappa':'\u03F0','varnothing':'\u2205','varphi':'\u03D5','varpi':'\u03D6','varpropto':'\u221D','varr':'\u2195','vArr':'\u21D5','varrho':'\u03F1','varsigma':'\u03C2','varsubsetneq':'\u228A\uFE00','varsubsetneqq':'\u2ACB\uFE00','varsupsetneq':'\u228B\uFE00','varsupsetneqq':'\u2ACC\uFE00','vartheta':'\u03D1','vartriangleleft':'\u22B2','vartriangleright':'\u22B3','vBar':'\u2AE8','Vbar':'\u2AEB','vBarv':'\u2AE9','vcy':'\u0432','Vcy':'\u0412','vdash':'\u22A2','vDash':'\u22A8','Vdash':'\u22A9','VDash':'\u22AB','Vdashl':'\u2AE6','vee':'\u2228','Vee':'\u22C1','veebar':'\u22BB','veeeq':'\u225A','vellip':'\u22EE','verbar':'|','Verbar':'\u2016','vert':'|','Vert':'\u2016','VerticalBar':'\u2223','VerticalLine':'|','VerticalSeparator':'\u2758','VerticalTilde':'\u2240','VeryThinSpace':'\u200A','vfr':'\uD835\uDD33','Vfr':'\uD835\uDD19','vltri':'\u22B2','vnsub':'\u2282\u20D2','vnsup':'\u2283\u20D2','vopf':'\uD835\uDD67','Vopf':'\uD835\uDD4D','vprop':'\u221D','vrtri':'\u22B3','vscr':'\uD835\uDCCB','Vscr':'\uD835\uDCB1','vsubne':'\u228A\uFE00','vsubnE':'\u2ACB\uFE00','vsupne':'\u228B\uFE00','vsupnE':'\u2ACC\uFE00','Vvdash':'\u22AA','vzigzag':'\u299A','wcirc':'\u0175','Wcirc':'\u0174','wedbar':'\u2A5F','wedge':'\u2227','Wedge':'\u22C0','wedgeq':'\u2259','weierp':'\u2118','wfr':'\uD835\uDD34','Wfr':'\uD835\uDD1A','wopf':'\uD835\uDD68','Wopf':'\uD835\uDD4E','wp':'\u2118','wr':'\u2240','wreath':'\u2240','wscr':'\uD835\uDCCC','Wscr':'\uD835\uDCB2','xcap':'\u22C2','xcirc':'\u25EF','xcup':'\u22C3','xdtri':'\u25BD','xfr':'\uD835\uDD35','Xfr':'\uD835\uDD1B','xharr':'\u27F7','xhArr':'\u27FA','xi':'\u03BE','Xi':'\u039E','xlarr':'\u27F5','xlArr':'\u27F8','xmap':'\u27FC','xnis':'\u22FB','xodot':'\u2A00','xopf':'\uD835\uDD69','Xopf':'\uD835\uDD4F','xoplus':'\u2A01','xotime':'\u2A02','xrarr':'\u27F6','xrArr':'\u27F9','xscr':'\uD835\uDCCD','Xscr':'\uD835\uDCB3','xsqcup':'\u2A06','xuplus':'\u2A04','xutri':'\u25B3','xvee':'\u22C1','xwedge':'\u22C0','yacute':'\xFD','Yacute':'\xDD','yacy':'\u044F','YAcy':'\u042F','ycirc':'\u0177','Ycirc':'\u0176','ycy':'\u044B','Ycy':'\u042B','yen':'\xA5','yfr':'\uD835\uDD36','Yfr':'\uD835\uDD1C','yicy':'\u0457','YIcy':'\u0407','yopf':'\uD835\uDD6A','Yopf':'\uD835\uDD50','yscr':'\uD835\uDCCE','Yscr':'\uD835\uDCB4','yucy':'\u044E','YUcy':'\u042E','yuml':'\xFF','Yuml':'\u0178','zacute':'\u017A','Zacute':'\u0179','zcaron':'\u017E','Zcaron':'\u017D','zcy':'\u0437','Zcy':'\u0417','zdot':'\u017C','Zdot':'\u017B','zeetrf':'\u2128','ZeroWidthSpace':'\u200B','zeta':'\u03B6','Zeta':'\u0396','zfr':'\uD835\uDD37','Zfr':'\u2128','zhcy':'\u0436','ZHcy':'\u0416','zigrarr':'\u21DD','zopf':'\uD835\uDD6B','Zopf':'\u2124','zscr':'\uD835\uDCCF','Zscr':'\uD835\uDCB5','zwj':'\u200D','zwnj':'\u200C'}; + var decodeMapLegacy = {'aacute':'\xE1','Aacute':'\xC1','acirc':'\xE2','Acirc':'\xC2','acute':'\xB4','aelig':'\xE6','AElig':'\xC6','agrave':'\xE0','Agrave':'\xC0','amp':'&','AMP':'&','aring':'\xE5','Aring':'\xC5','atilde':'\xE3','Atilde':'\xC3','auml':'\xE4','Auml':'\xC4','brvbar':'\xA6','ccedil':'\xE7','Ccedil':'\xC7','cedil':'\xB8','cent':'\xA2','copy':'\xA9','COPY':'\xA9','curren':'\xA4','deg':'\xB0','divide':'\xF7','eacute':'\xE9','Eacute':'\xC9','ecirc':'\xEA','Ecirc':'\xCA','egrave':'\xE8','Egrave':'\xC8','eth':'\xF0','ETH':'\xD0','euml':'\xEB','Euml':'\xCB','frac12':'\xBD','frac14':'\xBC','frac34':'\xBE','gt':'>','GT':'>','iacute':'\xED','Iacute':'\xCD','icirc':'\xEE','Icirc':'\xCE','iexcl':'\xA1','igrave':'\xEC','Igrave':'\xCC','iquest':'\xBF','iuml':'\xEF','Iuml':'\xCF','laquo':'\xAB','lt':'<','LT':'<','macr':'\xAF','micro':'\xB5','middot':'\xB7','nbsp':'\xA0','not':'\xAC','ntilde':'\xF1','Ntilde':'\xD1','oacute':'\xF3','Oacute':'\xD3','ocirc':'\xF4','Ocirc':'\xD4','ograve':'\xF2','Ograve':'\xD2','ordf':'\xAA','ordm':'\xBA','oslash':'\xF8','Oslash':'\xD8','otilde':'\xF5','Otilde':'\xD5','ouml':'\xF6','Ouml':'\xD6','para':'\xB6','plusmn':'\xB1','pound':'\xA3','quot':'"','QUOT':'"','raquo':'\xBB','reg':'\xAE','REG':'\xAE','sect':'\xA7','shy':'\xAD','sup1':'\xB9','sup2':'\xB2','sup3':'\xB3','szlig':'\xDF','thorn':'\xFE','THORN':'\xDE','times':'\xD7','uacute':'\xFA','Uacute':'\xDA','ucirc':'\xFB','Ucirc':'\xDB','ugrave':'\xF9','Ugrave':'\xD9','uml':'\xA8','uuml':'\xFC','Uuml':'\xDC','yacute':'\xFD','Yacute':'\xDD','yen':'\xA5','yuml':'\xFF'}; + var decodeMapNumeric = {'0':'\uFFFD','128':'\u20AC','130':'\u201A','131':'\u0192','132':'\u201E','133':'\u2026','134':'\u2020','135':'\u2021','136':'\u02C6','137':'\u2030','138':'\u0160','139':'\u2039','140':'\u0152','142':'\u017D','145':'\u2018','146':'\u2019','147':'\u201C','148':'\u201D','149':'\u2022','150':'\u2013','151':'\u2014','152':'\u02DC','153':'\u2122','154':'\u0161','155':'\u203A','156':'\u0153','158':'\u017E','159':'\u0178'}; + var invalidReferenceCodePoints = [1,2,3,4,5,6,7,8,11,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,64976,64977,64978,64979,64980,64981,64982,64983,64984,64985,64986,64987,64988,64989,64990,64991,64992,64993,64994,64995,64996,64997,64998,64999,65000,65001,65002,65003,65004,65005,65006,65007,65534,65535,131070,131071,196606,196607,262142,262143,327678,327679,393214,393215,458750,458751,524286,524287,589822,589823,655358,655359,720894,720895,786430,786431,851966,851967,917502,917503,983038,983039,1048574,1048575,1114110,1114111]; + + /*--------------------------------------------------------------------------*/ + + var stringFromCharCode = String.fromCharCode; + + var object = {}; + var hasOwnProperty = object.hasOwnProperty; + var has = function(object, propertyName) { + return hasOwnProperty.call(object, propertyName); + }; + + var contains = function(array, value) { + var index = -1; + var length = array.length; + while (++index < length) { + if (array[index] == value) { + return true; + } + } + return false; + }; + + var merge = function(options, defaults) { + if (!options) { + return defaults; + } + var result = {}; + var key; + for (key in defaults) { + // A `hasOwnProperty` check is not needed here, since only recognized + // option names are used anyway. Any others are ignored. + result[key] = has(options, key) ? options[key] : defaults[key]; + } + return result; + }; + + // Modified version of `ucs2encode`; see https://mths.be/punycode. + var codePointToSymbol = function(codePoint, strict) { + var output = ''; + if ((codePoint >= 0xD800 && codePoint <= 0xDFFF) || codePoint > 0x10FFFF) { + // See issue #4: + // โ€œOtherwise, if the number is in the range 0xD800 to 0xDFFF or is + // greater than 0x10FFFF, then this is a parse error. Return a U+FFFD + // REPLACEMENT CHARACTER.โ€ + if (strict) { + parseError('character reference outside the permissible Unicode range'); + } + return '\uFFFD'; + } + if (has(decodeMapNumeric, codePoint)) { + if (strict) { + parseError('disallowed character reference'); + } + return decodeMapNumeric[codePoint]; + } + if (strict && contains(invalidReferenceCodePoints, codePoint)) { + parseError('disallowed character reference'); + } + if (codePoint > 0xFFFF) { + codePoint -= 0x10000; + output += stringFromCharCode(codePoint >>> 10 & 0x3FF | 0xD800); + codePoint = 0xDC00 | codePoint & 0x3FF; + } + output += stringFromCharCode(codePoint); + return output; + }; + + var hexEscape = function(codePoint) { + return '&#x' + codePoint.toString(16).toUpperCase() + ';'; + }; + + var decEscape = function(codePoint) { + return '&#' + codePoint + ';'; + }; + + var parseError = function(message) { + throw Error('Parse error: ' + message); + }; + + /*--------------------------------------------------------------------------*/ + + var encode = function(string, options) { + options = merge(options, encode.options); + var strict = options.strict; + if (strict && regexInvalidRawCodePoint.test(string)) { + parseError('forbidden code point'); + } + var encodeEverything = options.encodeEverything; + var useNamedReferences = options.useNamedReferences; + var allowUnsafeSymbols = options.allowUnsafeSymbols; + var escapeCodePoint = options.decimal ? decEscape : hexEscape; + + var escapeBmpSymbol = function(symbol) { + return escapeCodePoint(symbol.charCodeAt(0)); + }; + + if (encodeEverything) { + // Encode ASCII symbols. + string = string.replace(regexAsciiWhitelist, function(symbol) { + // Use named references if requested & possible. + if (useNamedReferences && has(encodeMap, symbol)) { + return '&' + encodeMap[symbol] + ';'; + } + return escapeBmpSymbol(symbol); + }); + // Shorten a few escapes that represent two symbols, of which at least one + // is within the ASCII range. + if (useNamedReferences) { + string = string + .replace(/>\u20D2/g, '>⃒') + .replace(/<\u20D2/g, '<⃒') + .replace(/fj/g, 'fj'); + } + // Encode non-ASCII symbols. + if (useNamedReferences) { + // Encode non-ASCII symbols that can be replaced with a named reference. + string = string.replace(regexEncodeNonAscii, function(string) { + // Note: there is no need to check `has(encodeMap, string)` here. + return '&' + encodeMap[string] + ';'; + }); + } + // Note: any remaining non-ASCII symbols are handled outside of the `if`. + } else if (useNamedReferences) { + // Apply named character references. + // Encode `<>"'&` using named character references. + if (!allowUnsafeSymbols) { + string = string.replace(regexEscape, function(string) { + return '&' + encodeMap[string] + ';'; // no need to check `has()` here + }); + } + // Shorten escapes that represent two symbols, of which at least one is + // `<>"'&`. + string = string + .replace(/>\u20D2/g, '>⃒') + .replace(/<\u20D2/g, '<⃒'); + // Encode non-ASCII symbols that can be replaced with a named reference. + string = string.replace(regexEncodeNonAscii, function(string) { + // Note: there is no need to check `has(encodeMap, string)` here. + return '&' + encodeMap[string] + ';'; + }); + } else if (!allowUnsafeSymbols) { + // Encode `<>"'&` using hexadecimal escapes, now that theyโ€™re not handled + // using named character references. + string = string.replace(regexEscape, escapeBmpSymbol); + } + return string + // Encode astral symbols. + .replace(regexAstralSymbols, function($0) { + // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae + var high = $0.charCodeAt(0); + var low = $0.charCodeAt(1); + var codePoint = (high - 0xD800) * 0x400 + low - 0xDC00 + 0x10000; + return escapeCodePoint(codePoint); + }) + // Encode any remaining BMP symbols that are not printable ASCII symbols + // using a hexadecimal escape. + .replace(regexBmpWhitelist, escapeBmpSymbol); + }; + // Expose default options (so they can be overridden globally). + encode.options = { + 'allowUnsafeSymbols': false, + 'encodeEverything': false, + 'strict': false, + 'useNamedReferences': false, + 'decimal' : false + }; + + var decode = function(html, options) { + options = merge(options, decode.options); + var strict = options.strict; + if (strict && regexInvalidEntity.test(html)) { + parseError('malformed character reference'); + } + return html.replace(regexDecode, function($0, $1, $2, $3, $4, $5, $6, $7, $8) { + var codePoint; + var semicolon; + var decDigits; + var hexDigits; + var reference; + var next; + + if ($1) { + reference = $1; + // Note: there is no need to check `has(decodeMap, reference)`. + return decodeMap[reference]; + } + + if ($2) { + // Decode named character references without trailing `;`, e.g. `&`. + // This is only a parse error if it gets converted to `&`, or if it is + // followed by `=` in an attribute context. + reference = $2; + next = $3; + if (next && options.isAttributeValue) { + if (strict && next == '=') { + parseError('`&` did not start a character reference'); + } + return $0; + } else { + if (strict) { + parseError( + 'named character reference was not terminated by a semicolon' + ); + } + // Note: there is no need to check `has(decodeMapLegacy, reference)`. + return decodeMapLegacy[reference] + (next || ''); + } + } + + if ($4) { + // Decode decimal escapes, e.g. `𝌆`. + decDigits = $4; + semicolon = $5; + if (strict && !semicolon) { + parseError('character reference was not terminated by a semicolon'); + } + codePoint = parseInt(decDigits, 10); + return codePointToSymbol(codePoint, strict); + } + + if ($6) { + // Decode hexadecimal escapes, e.g. `𝌆`. + hexDigits = $6; + semicolon = $7; + if (strict && !semicolon) { + parseError('character reference was not terminated by a semicolon'); + } + codePoint = parseInt(hexDigits, 16); + return codePointToSymbol(codePoint, strict); + } + + // If weโ€™re still here, `if ($7)` is implied; itโ€™s an ambiguous + // ampersand for sure. https://mths.be/notes/ambiguous-ampersands + if (strict) { + parseError( + 'named character reference was not terminated by a semicolon' + ); + } + return $0; + }); + }; + // Expose default options (so they can be overridden globally). + decode.options = { + 'isAttributeValue': false, + 'strict': false + }; + + var escape = function(string) { + return string.replace(regexEscape, function($0) { + // Note: there is no need to check `has(escapeMap, $0)` here. + return escapeMap[$0]; + }); + }; + + /*--------------------------------------------------------------------------*/ + + var he = { + 'version': '1.2.0', + 'encode': encode, + 'decode': decode, + 'escape': escape, + 'unescape': decode + }; + + // Some AMD build optimizers, like r.js, check for specific condition patterns + // like the following: + if ( + false + ) { + define(function() { + return he; + }); + } else if (freeExports && !freeExports.nodeType) { + if (freeModule) { // in Node.js, io.js, or RingoJS v0.8.0+ + freeModule.exports = he; + } else { // in Narwhal or RingoJS v0.7.0- + for (var key in he) { + has(he, key) && (freeExports[key] = he[key]); + } + } + } else { // in Rhino or a web browser + root.he = he; + } + +}(this)); + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],55:[function(require,module,exports){ +exports.read = function (buffer, offset, isLE, mLen, nBytes) { + var e, m + var eLen = (nBytes * 8) - mLen - 1 + var eMax = (1 << eLen) - 1 + var eBias = eMax >> 1 + var nBits = -7 + var i = isLE ? (nBytes - 1) : 0 + var d = isLE ? -1 : 1 + var s = buffer[offset + i] + + i += d + + e = s & ((1 << (-nBits)) - 1) + s >>= (-nBits) + nBits += eLen + for (; nBits > 0; e = (e * 256) + buffer[offset + i], i += d, nBits -= 8) {} + + m = e & ((1 << (-nBits)) - 1) + e >>= (-nBits) + nBits += mLen + for (; nBits > 0; m = (m * 256) + buffer[offset + i], i += d, nBits -= 8) {} + + if (e === 0) { + e = 1 - eBias + } else if (e === eMax) { + return m ? NaN : ((s ? -1 : 1) * Infinity) + } else { + m = m + Math.pow(2, mLen) + e = e - eBias + } + return (s ? -1 : 1) * m * Math.pow(2, e - mLen) +} + +exports.write = function (buffer, value, offset, isLE, mLen, nBytes) { + var e, m, c + var eLen = (nBytes * 8) - mLen - 1 + var eMax = (1 << eLen) - 1 + var eBias = eMax >> 1 + var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0) + var i = isLE ? 0 : (nBytes - 1) + var d = isLE ? 1 : -1 + var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0 + + value = Math.abs(value) + + if (isNaN(value) || value === Infinity) { + m = isNaN(value) ? 1 : 0 + e = eMax + } else { + e = Math.floor(Math.log(value) / Math.LN2) + if (value * (c = Math.pow(2, -e)) < 1) { + e-- + c *= 2 + } + if (e + eBias >= 1) { + value += rt / c + } else { + value += rt * Math.pow(2, 1 - eBias) + } + if (value * c >= 2) { + e++ + c /= 2 + } + + if (e + eBias >= eMax) { + m = 0 + e = eMax + } else if (e + eBias >= 1) { + m = ((value * c) - 1) * Math.pow(2, mLen) + e = e + eBias + } else { + m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen) + e = 0 + } + } + + for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {} + + e = (e << mLen) | m + eLen += mLen + for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {} + + buffer[offset + i - d] |= s * 128 +} + +},{}],56:[function(require,module,exports){ +if (typeof Object.create === 'function') { + // implementation from standard node.js 'util' module + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } }); }; +} else { + // old school shim for old browsers + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + var TempCtor = function () {} + TempCtor.prototype = superCtor.prototype + ctor.prototype = new TempCtor() + ctor.prototype.constructor = ctor + } +} + +},{}],57:[function(require,module,exports){ +/*! + * Determine if an object is a Buffer + * + * @author Feross Aboukhadijeh + * @license MIT + */ + +// The _isBuffer check is for Safari 5-7 support, because it's missing +// Object.prototype.constructor. Remove this eventually +module.exports = function (obj) { + return obj != null && (isBuffer(obj) || isSlowBuffer(obj) || !!obj._isBuffer) +} + +function isBuffer (obj) { + return !!obj.constructor && typeof obj.constructor.isBuffer === 'function' && obj.constructor.isBuffer(obj) +} + +// For Node v0.10 support. Remove this eventually. +function isSlowBuffer (obj) { + return typeof obj.readFloatLE === 'function' && typeof obj.slice === 'function' && isBuffer(obj.slice(0, 0)) +} + +},{}],58:[function(require,module,exports){ +var toString = {}.toString; + +module.exports = Array.isArray || function (arr) { + return toString.call(arr) == '[object Array]'; +}; + +},{}],59:[function(require,module,exports){ +var path = require('path'); +var fs = require('fs'); +var _0777 = parseInt('0777', 8); + +module.exports = mkdirP.mkdirp = mkdirP.mkdirP = mkdirP; + +function mkdirP (p, opts, f, made) { + if (typeof opts === 'function') { + f = opts; + opts = {}; + } + else if (!opts || typeof opts !== 'object') { + opts = { mode: opts }; + } + + var mode = opts.mode; + var xfs = opts.fs || fs; + + if (mode === undefined) { + mode = _0777 + } + if (!made) made = null; + + var cb = f || function () {}; + p = path.resolve(p); + + xfs.mkdir(p, mode, function (er) { + if (!er) { + made = made || p; + return cb(null, made); + } + switch (er.code) { + case 'ENOENT': + if (path.dirname(p) === p) return cb(er); + mkdirP(path.dirname(p), opts, function (er, made) { + if (er) cb(er, made); + else mkdirP(p, opts, cb, made); + }); + break; + + // In the case of any other error, just see if there's a dir + // there already. If so, then hooray! If not, then something + // is borked. + default: + xfs.stat(p, function (er2, stat) { + // if the stat fails, then that's super weird. + // let the original error be the failure reason. + if (er2 || !stat.isDirectory()) cb(er, made) + else cb(null, made); + }); + break; + } + }); +} + +mkdirP.sync = function sync (p, opts, made) { + if (!opts || typeof opts !== 'object') { + opts = { mode: opts }; + } + + var mode = opts.mode; + var xfs = opts.fs || fs; + + if (mode === undefined) { + mode = _0777 + } + if (!made) made = null; + + p = path.resolve(p); + + try { + xfs.mkdirSync(p, mode); + made = made || p; + } + catch (err0) { + switch (err0.code) { + case 'ENOENT' : + made = sync(path.dirname(p), opts, made); + sync(p, opts, made); + break; + + // In the case of any other error, just see if there's a dir + // there already. If so, then hooray! If not, then something + // is borked. + default: + var stat; + try { + stat = xfs.statSync(p); + } + catch (err1) { + throw err0; + } + if (!stat.isDirectory()) throw err0; + break; + } + } + + return made; +}; + +},{"fs":42,"path":42}],60:[function(require,module,exports){ +/** + * Helpers. + */ + +var s = 1000; +var m = s * 60; +var h = m * 60; +var d = h * 24; +var w = d * 7; +var y = d * 365.25; + +/** + * Parse or format the given `val`. + * + * Options: + * + * - `long` verbose formatting [false] + * + * @param {String|Number} val + * @param {Object} [options] + * @throws {Error} throw an error if val is not a non-empty string or a number + * @return {String|Number} + * @api public + */ + +module.exports = function(val, options) { + options = options || {}; + var type = typeof val; + if (type === 'string' && val.length > 0) { + return parse(val); + } else if (type === 'number' && isNaN(val) === false) { + return options.long ? fmtLong(val) : fmtShort(val); + } + throw new Error( + 'val is not a non-empty string or a valid number. val=' + + JSON.stringify(val) + ); +}; + +/** + * Parse the given `str` and return milliseconds. + * + * @param {String} str + * @return {Number} + * @api private + */ + +function parse(str) { + str = String(str); + if (str.length > 100) { + return; + } + var match = /^((?:\d+)?\-?\d?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec( + str + ); + if (!match) { + return; + } + var n = parseFloat(match[1]); + var type = (match[2] || 'ms').toLowerCase(); + switch (type) { + case 'years': + case 'year': + case 'yrs': + case 'yr': + case 'y': + return n * y; + case 'weeks': + case 'week': + case 'w': + return n * w; + case 'days': + case 'day': + case 'd': + return n * d; + case 'hours': + case 'hour': + case 'hrs': + case 'hr': + case 'h': + return n * h; + case 'minutes': + case 'minute': + case 'mins': + case 'min': + case 'm': + return n * m; + case 'seconds': + case 'second': + case 'secs': + case 'sec': + case 's': + return n * s; + case 'milliseconds': + case 'millisecond': + case 'msecs': + case 'msec': + case 'ms': + return n; + default: + return undefined; + } +} + +/** + * Short format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + +function fmtShort(ms) { + var msAbs = Math.abs(ms); + if (msAbs >= d) { + return Math.round(ms / d) + 'd'; + } + if (msAbs >= h) { + return Math.round(ms / h) + 'h'; + } + if (msAbs >= m) { + return Math.round(ms / m) + 'm'; + } + if (msAbs >= s) { + return Math.round(ms / s) + 's'; + } + return ms + 'ms'; +} + +/** + * Long format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + +function fmtLong(ms) { + var msAbs = Math.abs(ms); + if (msAbs >= d) { + return plural(ms, msAbs, d, 'day'); + } + if (msAbs >= h) { + return plural(ms, msAbs, h, 'hour'); + } + if (msAbs >= m) { + return plural(ms, msAbs, m, 'minute'); + } + if (msAbs >= s) { + return plural(ms, msAbs, s, 'second'); + } + return ms + ' ms'; +} + +/** + * Pluralization helper. + */ + +function plural(ms, msAbs, n, name) { + var isPlural = msAbs >= n * 1.5; + return Math.round(ms / n) + ' ' + name + (isPlural ? 's' : ''); +} + +},{}],61:[function(require,module,exports){ +'use strict'; + +var keysShim; +if (!Object.keys) { + // modified from https://github.com/es-shims/es5-shim + var has = Object.prototype.hasOwnProperty; + var toStr = Object.prototype.toString; + var isArgs = require('./isArguments'); // eslint-disable-line global-require + var isEnumerable = Object.prototype.propertyIsEnumerable; + var hasDontEnumBug = !isEnumerable.call({ toString: null }, 'toString'); + var hasProtoEnumBug = isEnumerable.call(function () {}, 'prototype'); + var dontEnums = [ + 'toString', + 'toLocaleString', + 'valueOf', + 'hasOwnProperty', + 'isPrototypeOf', + 'propertyIsEnumerable', + 'constructor' + ]; + var equalsConstructorPrototype = function (o) { + var ctor = o.constructor; + return ctor && ctor.prototype === o; + }; + var excludedKeys = { + $applicationCache: true, + $console: true, + $external: true, + $frame: true, + $frameElement: true, + $frames: true, + $innerHeight: true, + $innerWidth: true, + $outerHeight: true, + $outerWidth: true, + $pageXOffset: true, + $pageYOffset: true, + $parent: true, + $scrollLeft: true, + $scrollTop: true, + $scrollX: true, + $scrollY: true, + $self: true, + $webkitIndexedDB: true, + $webkitStorageInfo: true, + $window: true + }; + var hasAutomationEqualityBug = (function () { + /* global window */ + if (typeof window === 'undefined') { return false; } + for (var k in window) { + try { + if (!excludedKeys['$' + k] && has.call(window, k) && window[k] !== null && typeof window[k] === 'object') { + try { + equalsConstructorPrototype(window[k]); + } catch (e) { + return true; + } + } + } catch (e) { + return true; + } + } + return false; + }()); + var equalsConstructorPrototypeIfNotBuggy = function (o) { + /* global window */ + if (typeof window === 'undefined' || !hasAutomationEqualityBug) { + return equalsConstructorPrototype(o); + } + try { + return equalsConstructorPrototype(o); + } catch (e) { + return false; + } + }; + + keysShim = function keys(object) { + var isObject = object !== null && typeof object === 'object'; + var isFunction = toStr.call(object) === '[object Function]'; + var isArguments = isArgs(object); + var isString = isObject && toStr.call(object) === '[object String]'; + var theKeys = []; + + if (!isObject && !isFunction && !isArguments) { + throw new TypeError('Object.keys called on a non-object'); + } + + var skipProto = hasProtoEnumBug && isFunction; + if (isString && object.length > 0 && !has.call(object, 0)) { + for (var i = 0; i < object.length; ++i) { + theKeys.push(String(i)); + } + } + + if (isArguments && object.length > 0) { + for (var j = 0; j < object.length; ++j) { + theKeys.push(String(j)); + } + } else { + for (var name in object) { + if (!(skipProto && name === 'prototype') && has.call(object, name)) { + theKeys.push(String(name)); + } + } + } + + if (hasDontEnumBug) { + var skipConstructor = equalsConstructorPrototypeIfNotBuggy(object); + + for (var k = 0; k < dontEnums.length; ++k) { + if (!(skipConstructor && dontEnums[k] === 'constructor') && has.call(object, dontEnums[k])) { + theKeys.push(dontEnums[k]); + } + } + } + return theKeys; + }; +} +module.exports = keysShim; + +},{"./isArguments":63}],62:[function(require,module,exports){ +'use strict'; + +var slice = Array.prototype.slice; +var isArgs = require('./isArguments'); + +var origKeys = Object.keys; +var keysShim = origKeys ? function keys(o) { return origKeys(o); } : require('./implementation'); + +var originalKeys = Object.keys; + +keysShim.shim = function shimObjectKeys() { + if (Object.keys) { + var keysWorksWithArguments = (function () { + // Safari 5.0 bug + var args = Object.keys(arguments); + return args && args.length === arguments.length; + }(1, 2)); + if (!keysWorksWithArguments) { + Object.keys = function keys(object) { // eslint-disable-line func-name-matching + if (isArgs(object)) { + return originalKeys(slice.call(object)); + } + return originalKeys(object); + }; + } + } else { + Object.keys = keysShim; + } + return Object.keys || keysShim; +}; + +module.exports = keysShim; + +},{"./implementation":61,"./isArguments":63}],63:[function(require,module,exports){ +'use strict'; + +var toStr = Object.prototype.toString; + +module.exports = function isArguments(value) { + var str = toStr.call(value); + var isArgs = str === '[object Arguments]'; + if (!isArgs) { + isArgs = str !== '[object Array]' && + value !== null && + typeof value === 'object' && + typeof value.length === 'number' && + value.length >= 0 && + toStr.call(value.callee) === '[object Function]'; + } + return isArgs; +}; + +},{}],64:[function(require,module,exports){ +'use strict'; + +// modified from https://github.com/es-shims/es6-shim +var keys = require('object-keys'); +var bind = require('function-bind'); +var canBeObject = function (obj) { + return typeof obj !== 'undefined' && obj !== null; +}; +var hasSymbols = require('has-symbols/shams')(); +var toObject = Object; +var push = bind.call(Function.call, Array.prototype.push); +var propIsEnumerable = bind.call(Function.call, Object.prototype.propertyIsEnumerable); +var originalGetSymbols = hasSymbols ? Object.getOwnPropertySymbols : null; + +module.exports = function assign(target, source1) { + if (!canBeObject(target)) { throw new TypeError('target must be an object'); } + var objTarget = toObject(target); + var s, source, i, props, syms, value, key; + for (s = 1; s < arguments.length; ++s) { + source = toObject(arguments[s]); + props = keys(source); + var getSymbols = hasSymbols && (Object.getOwnPropertySymbols || originalGetSymbols); + if (getSymbols) { + syms = getSymbols(source); + for (i = 0; i < syms.length; ++i) { + key = syms[i]; + if (propIsEnumerable(source, key)) { + push(props, key); + } + } + } + for (i = 0; i < props.length; ++i) { + key = props[i]; + value = source[key]; + if (propIsEnumerable(source, key)) { + objTarget[key] = value; + } + } + } + return objTarget; +}; + +},{"function-bind":52,"has-symbols/shams":53,"object-keys":62}],65:[function(require,module,exports){ +'use strict'; + +var defineProperties = require('define-properties'); + +var implementation = require('./implementation'); +var getPolyfill = require('./polyfill'); +var shim = require('./shim'); + +var polyfill = getPolyfill(); + +defineProperties(polyfill, { + getPolyfill: getPolyfill, + implementation: implementation, + shim: shim +}); + +module.exports = polyfill; + +},{"./implementation":64,"./polyfill":66,"./shim":67,"define-properties":47}],66:[function(require,module,exports){ +'use strict'; + +var implementation = require('./implementation'); + +var lacksProperEnumerationOrder = function () { + if (!Object.assign) { + return false; + } + // v8, specifically in node 4.x, has a bug with incorrect property enumeration order + // note: this does not detect the bug unless there's 20 characters + var str = 'abcdefghijklmnopqrst'; + var letters = str.split(''); + var map = {}; + for (var i = 0; i < letters.length; ++i) { + map[letters[i]] = letters[i]; + } + var obj = Object.assign({}, map); + var actual = ''; + for (var k in obj) { + actual += k; + } + return str !== actual; +}; + +var assignHasPendingExceptions = function () { + if (!Object.assign || !Object.preventExtensions) { + return false; + } + // Firefox 37 still has "pending exception" logic in its Object.assign implementation, + // which is 72% slower than our shim, and Firefox 40's native implementation. + var thrower = Object.preventExtensions({ 1: 2 }); + try { + Object.assign(thrower, 'xy'); + } catch (e) { + return thrower[1] === 'y'; + } + return false; +}; + +module.exports = function getPolyfill() { + if (!Object.assign) { + return implementation; + } + if (lacksProperEnumerationOrder()) { + return implementation; + } + if (assignHasPendingExceptions()) { + return implementation; + } + return Object.assign; +}; + +},{"./implementation":64}],67:[function(require,module,exports){ +'use strict'; + +var define = require('define-properties'); +var getPolyfill = require('./polyfill'); + +module.exports = function shimAssign() { + var polyfill = getPolyfill(); + define( + Object, + { assign: polyfill }, + { assign: function () { return Object.assign !== polyfill; } } + ); + return polyfill; +}; + +},{"./polyfill":66,"define-properties":47}],68:[function(require,module,exports){ +(function (process){ +'use strict'; + +if (!process.version || + process.version.indexOf('v0.') === 0 || + process.version.indexOf('v1.') === 0 && process.version.indexOf('v1.8.') !== 0) { + module.exports = { nextTick: nextTick }; +} else { + module.exports = process +} + +function nextTick(fn, arg1, arg2, arg3) { + if (typeof fn !== 'function') { + throw new TypeError('"callback" argument must be a function'); + } + var len = arguments.length; + var args, i; + switch (len) { + case 0: + case 1: + return process.nextTick(fn); + case 2: + return process.nextTick(function afterTickOne() { + fn.call(null, arg1); + }); + case 3: + return process.nextTick(function afterTickTwo() { + fn.call(null, arg1, arg2); + }); + case 4: + return process.nextTick(function afterTickThree() { + fn.call(null, arg1, arg2, arg3); + }); + default: + args = new Array(len - 1); + i = 0; + while (i < args.length) { + args[i++] = arguments[i]; + } + return process.nextTick(function afterTick() { + fn.apply(null, args); + }); + } +} + + +}).call(this,require('_process')) +},{"_process":69}],69:[function(require,module,exports){ +// shim for using process in browser +var process = module.exports = {}; + +// cached from whatever global is present so that test runners that stub it +// don't break things. But we need to wrap it in a try catch in case it is +// wrapped in strict mode code which doesn't define any globals. It's inside a +// function because try/catches deoptimize in certain engines. + +var cachedSetTimeout; +var cachedClearTimeout; + +function defaultSetTimout() { + throw new Error('setTimeout has not been defined'); +} +function defaultClearTimeout () { + throw new Error('clearTimeout has not been defined'); +} +(function () { + try { + if (typeof setTimeout === 'function') { + cachedSetTimeout = setTimeout; + } else { + cachedSetTimeout = defaultSetTimout; + } + } catch (e) { + cachedSetTimeout = defaultSetTimout; + } + try { + if (typeof clearTimeout === 'function') { + cachedClearTimeout = clearTimeout; + } else { + cachedClearTimeout = defaultClearTimeout; + } + } catch (e) { + cachedClearTimeout = defaultClearTimeout; + } +} ()) +function runTimeout(fun) { + if (cachedSetTimeout === setTimeout) { + //normal enviroments in sane situations + return setTimeout(fun, 0); + } + // if setTimeout wasn't available but was latter defined + if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { + cachedSetTimeout = setTimeout; + return setTimeout(fun, 0); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedSetTimeout(fun, 0); + } catch(e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedSetTimeout.call(null, fun, 0); + } catch(e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error + return cachedSetTimeout.call(this, fun, 0); + } + } + + +} +function runClearTimeout(marker) { + if (cachedClearTimeout === clearTimeout) { + //normal enviroments in sane situations + return clearTimeout(marker); + } + // if clearTimeout wasn't available but was latter defined + if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { + cachedClearTimeout = clearTimeout; + return clearTimeout(marker); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedClearTimeout(marker); + } catch (e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedClearTimeout.call(null, marker); + } catch (e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. + // Some versions of I.E. have different rules for clearTimeout vs setTimeout + return cachedClearTimeout.call(this, marker); + } + } + + + +} +var queue = []; +var draining = false; +var currentQueue; +var queueIndex = -1; + +function cleanUpNextTick() { + if (!draining || !currentQueue) { + return; + } + draining = false; + if (currentQueue.length) { + queue = currentQueue.concat(queue); + } else { + queueIndex = -1; + } + if (queue.length) { + drainQueue(); + } +} + +function drainQueue() { + if (draining) { + return; + } + var timeout = runTimeout(cleanUpNextTick); + draining = true; + + var len = queue.length; + while(len) { + currentQueue = queue; + queue = []; + while (++queueIndex < len) { + if (currentQueue) { + currentQueue[queueIndex].run(); + } + } + queueIndex = -1; + len = queue.length; + } + currentQueue = null; + draining = false; + runClearTimeout(timeout); +} + +process.nextTick = function (fun) { + var args = new Array(arguments.length - 1); + if (arguments.length > 1) { + for (var i = 1; i < arguments.length; i++) { + args[i - 1] = arguments[i]; + } + } + queue.push(new Item(fun, args)); + if (queue.length === 1 && !draining) { + runTimeout(drainQueue); + } +}; + +// v8 likes predictible objects +function Item(fun, array) { + this.fun = fun; + this.array = array; +} +Item.prototype.run = function () { + this.fun.apply(null, this.array); +}; +process.title = 'browser'; +process.browser = true; +process.env = {}; +process.argv = []; +process.version = ''; // empty string to avoid regexp issues +process.versions = {}; + +function noop() {} + +process.on = noop; +process.addListener = noop; +process.once = noop; +process.off = noop; +process.removeListener = noop; +process.removeAllListeners = noop; +process.emit = noop; +process.prependListener = noop; +process.prependOnceListener = noop; + +process.listeners = function (name) { return [] } + +process.binding = function (name) { + throw new Error('process.binding is not supported'); +}; + +process.cwd = function () { return '/' }; +process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); +}; +process.umask = function() { return 0; }; + +},{}],70:[function(require,module,exports){ +module.exports = require('./lib/_stream_duplex.js'); + +},{"./lib/_stream_duplex.js":71}],71:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// a duplex stream is just a stream that is both readable and writable. +// Since JS doesn't have multiple prototypal inheritance, this class +// prototypally inherits from Readable, and then parasitically from +// Writable. + +'use strict'; + +/**/ + +var pna = require('process-nextick-args'); +/**/ + +/**/ +var objectKeys = Object.keys || function (obj) { + var keys = []; + for (var key in obj) { + keys.push(key); + }return keys; +}; +/**/ + +module.exports = Duplex; + +/**/ +var util = require('core-util-is'); +util.inherits = require('inherits'); +/**/ + +var Readable = require('./_stream_readable'); +var Writable = require('./_stream_writable'); + +util.inherits(Duplex, Readable); + +{ + // avoid scope creep, the keys array can then be collected + var keys = objectKeys(Writable.prototype); + for (var v = 0; v < keys.length; v++) { + var method = keys[v]; + if (!Duplex.prototype[method]) Duplex.prototype[method] = Writable.prototype[method]; + } +} + +function Duplex(options) { + if (!(this instanceof Duplex)) return new Duplex(options); + + Readable.call(this, options); + Writable.call(this, options); + + if (options && options.readable === false) this.readable = false; + + if (options && options.writable === false) this.writable = false; + + this.allowHalfOpen = true; + if (options && options.allowHalfOpen === false) this.allowHalfOpen = false; + + this.once('end', onend); +} + +Object.defineProperty(Duplex.prototype, 'writableHighWaterMark', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function () { + return this._writableState.highWaterMark; + } +}); + +// the no-half-open enforcer +function onend() { + // if we allow half-open state, or if the writable side ended, + // then we're ok. + if (this.allowHalfOpen || this._writableState.ended) return; + + // no more data can be written. + // But allow more writes to happen in this tick. + pna.nextTick(onEndNT, this); +} + +function onEndNT(self) { + self.end(); +} + +Object.defineProperty(Duplex.prototype, 'destroyed', { + get: function () { + if (this._readableState === undefined || this._writableState === undefined) { + return false; + } + return this._readableState.destroyed && this._writableState.destroyed; + }, + set: function (value) { + // we ignore the value if the stream + // has not been initialized yet + if (this._readableState === undefined || this._writableState === undefined) { + return; + } + + // backward compatibility, the user is explicitly + // managing destroyed + this._readableState.destroyed = value; + this._writableState.destroyed = value; + } +}); + +Duplex.prototype._destroy = function (err, cb) { + this.push(null); + this.end(); + + pna.nextTick(cb, err); +}; +},{"./_stream_readable":73,"./_stream_writable":75,"core-util-is":44,"inherits":56,"process-nextick-args":68}],72:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// a passthrough stream. +// basically just the most minimal sort of Transform stream. +// Every written chunk gets output as-is. + +'use strict'; + +module.exports = PassThrough; + +var Transform = require('./_stream_transform'); + +/**/ +var util = require('core-util-is'); +util.inherits = require('inherits'); +/**/ + +util.inherits(PassThrough, Transform); + +function PassThrough(options) { + if (!(this instanceof PassThrough)) return new PassThrough(options); + + Transform.call(this, options); +} + +PassThrough.prototype._transform = function (chunk, encoding, cb) { + cb(null, chunk); +}; +},{"./_stream_transform":74,"core-util-is":44,"inherits":56}],73:[function(require,module,exports){ +(function (process,global){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +/**/ + +var pna = require('process-nextick-args'); +/**/ + +module.exports = Readable; + +/**/ +var isArray = require('isarray'); +/**/ + +/**/ +var Duplex; +/**/ + +Readable.ReadableState = ReadableState; + +/**/ +var EE = require('events').EventEmitter; + +var EElistenerCount = function (emitter, type) { + return emitter.listeners(type).length; +}; +/**/ + +/**/ +var Stream = require('./internal/streams/stream'); +/**/ + +/**/ + +var Buffer = require('safe-buffer').Buffer; +var OurUint8Array = global.Uint8Array || function () {}; +function _uint8ArrayToBuffer(chunk) { + return Buffer.from(chunk); +} +function _isUint8Array(obj) { + return Buffer.isBuffer(obj) || obj instanceof OurUint8Array; +} + +/**/ + +/**/ +var util = require('core-util-is'); +util.inherits = require('inherits'); +/**/ + +/**/ +var debugUtil = require('util'); +var debug = void 0; +if (debugUtil && debugUtil.debuglog) { + debug = debugUtil.debuglog('stream'); +} else { + debug = function () {}; +} +/**/ + +var BufferList = require('./internal/streams/BufferList'); +var destroyImpl = require('./internal/streams/destroy'); +var StringDecoder; + +util.inherits(Readable, Stream); + +var kProxyEvents = ['error', 'close', 'destroy', 'pause', 'resume']; + +function prependListener(emitter, event, fn) { + // Sadly this is not cacheable as some libraries bundle their own + // event emitter implementation with them. + if (typeof emitter.prependListener === 'function') return emitter.prependListener(event, fn); + + // This is a hack to make sure that our error handler is attached before any + // userland ones. NEVER DO THIS. This is here only because this code needs + // to continue to work with older versions of Node.js that do not include + // the prependListener() method. The goal is to eventually remove this hack. + if (!emitter._events || !emitter._events[event]) emitter.on(event, fn);else if (isArray(emitter._events[event])) emitter._events[event].unshift(fn);else emitter._events[event] = [fn, emitter._events[event]]; +} + +function ReadableState(options, stream) { + Duplex = Duplex || require('./_stream_duplex'); + + options = options || {}; + + // Duplex streams are both readable and writable, but share + // the same options object. + // However, some cases require setting options to different + // values for the readable and the writable sides of the duplex stream. + // These options can be provided separately as readableXXX and writableXXX. + var isDuplex = stream instanceof Duplex; + + // object stream flag. Used to make read(n) ignore n and to + // make all the buffer merging and length checks go away + this.objectMode = !!options.objectMode; + + if (isDuplex) this.objectMode = this.objectMode || !!options.readableObjectMode; + + // the point at which it stops calling _read() to fill the buffer + // Note: 0 is a valid value, means "don't call _read preemptively ever" + var hwm = options.highWaterMark; + var readableHwm = options.readableHighWaterMark; + var defaultHwm = this.objectMode ? 16 : 16 * 1024; + + if (hwm || hwm === 0) this.highWaterMark = hwm;else if (isDuplex && (readableHwm || readableHwm === 0)) this.highWaterMark = readableHwm;else this.highWaterMark = defaultHwm; + + // cast to ints. + this.highWaterMark = Math.floor(this.highWaterMark); + + // A linked list is used to store data chunks instead of an array because the + // linked list can remove elements from the beginning faster than + // array.shift() + this.buffer = new BufferList(); + this.length = 0; + this.pipes = null; + this.pipesCount = 0; + this.flowing = null; + this.ended = false; + this.endEmitted = false; + this.reading = false; + + // a flag to be able to tell if the event 'readable'/'data' is emitted + // immediately, or on a later tick. We set this to true at first, because + // any actions that shouldn't happen until "later" should generally also + // not happen before the first read call. + this.sync = true; + + // whenever we return null, then we set a flag to say + // that we're awaiting a 'readable' event emission. + this.needReadable = false; + this.emittedReadable = false; + this.readableListening = false; + this.resumeScheduled = false; + + // has it been destroyed + this.destroyed = false; + + // Crypto is kind of old and crusty. Historically, its default string + // encoding is 'binary' so we have to make this configurable. + // Everything else in the universe uses 'utf8', though. + this.defaultEncoding = options.defaultEncoding || 'utf8'; + + // the number of writers that are awaiting a drain event in .pipe()s + this.awaitDrain = 0; + + // if true, a maybeReadMore has been scheduled + this.readingMore = false; + + this.decoder = null; + this.encoding = null; + if (options.encoding) { + if (!StringDecoder) StringDecoder = require('string_decoder/').StringDecoder; + this.decoder = new StringDecoder(options.encoding); + this.encoding = options.encoding; + } +} + +function Readable(options) { + Duplex = Duplex || require('./_stream_duplex'); + + if (!(this instanceof Readable)) return new Readable(options); + + this._readableState = new ReadableState(options, this); + + // legacy + this.readable = true; + + if (options) { + if (typeof options.read === 'function') this._read = options.read; + + if (typeof options.destroy === 'function') this._destroy = options.destroy; + } + + Stream.call(this); +} + +Object.defineProperty(Readable.prototype, 'destroyed', { + get: function () { + if (this._readableState === undefined) { + return false; + } + return this._readableState.destroyed; + }, + set: function (value) { + // we ignore the value if the stream + // has not been initialized yet + if (!this._readableState) { + return; + } + + // backward compatibility, the user is explicitly + // managing destroyed + this._readableState.destroyed = value; + } +}); + +Readable.prototype.destroy = destroyImpl.destroy; +Readable.prototype._undestroy = destroyImpl.undestroy; +Readable.prototype._destroy = function (err, cb) { + this.push(null); + cb(err); +}; + +// Manually shove something into the read() buffer. +// This returns true if the highWaterMark has not been hit yet, +// similar to how Writable.write() returns true if you should +// write() some more. +Readable.prototype.push = function (chunk, encoding) { + var state = this._readableState; + var skipChunkCheck; + + if (!state.objectMode) { + if (typeof chunk === 'string') { + encoding = encoding || state.defaultEncoding; + if (encoding !== state.encoding) { + chunk = Buffer.from(chunk, encoding); + encoding = ''; + } + skipChunkCheck = true; + } + } else { + skipChunkCheck = true; + } + + return readableAddChunk(this, chunk, encoding, false, skipChunkCheck); +}; + +// Unshift should *always* be something directly out of read() +Readable.prototype.unshift = function (chunk) { + return readableAddChunk(this, chunk, null, true, false); +}; + +function readableAddChunk(stream, chunk, encoding, addToFront, skipChunkCheck) { + var state = stream._readableState; + if (chunk === null) { + state.reading = false; + onEofChunk(stream, state); + } else { + var er; + if (!skipChunkCheck) er = chunkInvalid(state, chunk); + if (er) { + stream.emit('error', er); + } else if (state.objectMode || chunk && chunk.length > 0) { + if (typeof chunk !== 'string' && !state.objectMode && Object.getPrototypeOf(chunk) !== Buffer.prototype) { + chunk = _uint8ArrayToBuffer(chunk); + } + + if (addToFront) { + if (state.endEmitted) stream.emit('error', new Error('stream.unshift() after end event'));else addChunk(stream, state, chunk, true); + } else if (state.ended) { + stream.emit('error', new Error('stream.push() after EOF')); + } else { + state.reading = false; + if (state.decoder && !encoding) { + chunk = state.decoder.write(chunk); + if (state.objectMode || chunk.length !== 0) addChunk(stream, state, chunk, false);else maybeReadMore(stream, state); + } else { + addChunk(stream, state, chunk, false); + } + } + } else if (!addToFront) { + state.reading = false; + } + } + + return needMoreData(state); +} + +function addChunk(stream, state, chunk, addToFront) { + if (state.flowing && state.length === 0 && !state.sync) { + stream.emit('data', chunk); + stream.read(0); + } else { + // update the buffer info. + state.length += state.objectMode ? 1 : chunk.length; + if (addToFront) state.buffer.unshift(chunk);else state.buffer.push(chunk); + + if (state.needReadable) emitReadable(stream); + } + maybeReadMore(stream, state); +} + +function chunkInvalid(state, chunk) { + var er; + if (!_isUint8Array(chunk) && typeof chunk !== 'string' && chunk !== undefined && !state.objectMode) { + er = new TypeError('Invalid non-string/buffer chunk'); + } + return er; +} + +// if it's past the high water mark, we can push in some more. +// Also, if we have no data yet, we can stand some +// more bytes. This is to work around cases where hwm=0, +// such as the repl. Also, if the push() triggered a +// readable event, and the user called read(largeNumber) such that +// needReadable was set, then we ought to push more, so that another +// 'readable' event will be triggered. +function needMoreData(state) { + return !state.ended && (state.needReadable || state.length < state.highWaterMark || state.length === 0); +} + +Readable.prototype.isPaused = function () { + return this._readableState.flowing === false; +}; + +// backwards compatibility. +Readable.prototype.setEncoding = function (enc) { + if (!StringDecoder) StringDecoder = require('string_decoder/').StringDecoder; + this._readableState.decoder = new StringDecoder(enc); + this._readableState.encoding = enc; + return this; +}; + +// Don't raise the hwm > 8MB +var MAX_HWM = 0x800000; +function computeNewHighWaterMark(n) { + if (n >= MAX_HWM) { + n = MAX_HWM; + } else { + // Get the next highest power of 2 to prevent increasing hwm excessively in + // tiny amounts + n--; + n |= n >>> 1; + n |= n >>> 2; + n |= n >>> 4; + n |= n >>> 8; + n |= n >>> 16; + n++; + } + return n; +} + +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function howMuchToRead(n, state) { + if (n <= 0 || state.length === 0 && state.ended) return 0; + if (state.objectMode) return 1; + if (n !== n) { + // Only flow one buffer at a time + if (state.flowing && state.length) return state.buffer.head.data.length;else return state.length; + } + // If we're asking for more than the current hwm, then raise the hwm. + if (n > state.highWaterMark) state.highWaterMark = computeNewHighWaterMark(n); + if (n <= state.length) return n; + // Don't have enough + if (!state.ended) { + state.needReadable = true; + return 0; + } + return state.length; +} + +// you can override either this method, or the async _read(n) below. +Readable.prototype.read = function (n) { + debug('read', n); + n = parseInt(n, 10); + var state = this._readableState; + var nOrig = n; + + if (n !== 0) state.emittedReadable = false; + + // if we're doing read(0) to trigger a readable event, but we + // already have a bunch of data in the buffer, then just trigger + // the 'readable' event and move on. + if (n === 0 && state.needReadable && (state.length >= state.highWaterMark || state.ended)) { + debug('read: emitReadable', state.length, state.ended); + if (state.length === 0 && state.ended) endReadable(this);else emitReadable(this); + return null; + } + + n = howMuchToRead(n, state); + + // if we've ended, and we're now clear, then finish it up. + if (n === 0 && state.ended) { + if (state.length === 0) endReadable(this); + return null; + } + + // All the actual chunk generation logic needs to be + // *below* the call to _read. The reason is that in certain + // synthetic stream cases, such as passthrough streams, _read + // may be a completely synchronous operation which may change + // the state of the read buffer, providing enough data when + // before there was *not* enough. + // + // So, the steps are: + // 1. Figure out what the state of things will be after we do + // a read from the buffer. + // + // 2. If that resulting state will trigger a _read, then call _read. + // Note that this may be asynchronous, or synchronous. Yes, it is + // deeply ugly to write APIs this way, but that still doesn't mean + // that the Readable class should behave improperly, as streams are + // designed to be sync/async agnostic. + // Take note if the _read call is sync or async (ie, if the read call + // has returned yet), so that we know whether or not it's safe to emit + // 'readable' etc. + // + // 3. Actually pull the requested chunks out of the buffer and return. + + // if we need a readable event, then we need to do some reading. + var doRead = state.needReadable; + debug('need readable', doRead); + + // if we currently have less than the highWaterMark, then also read some + if (state.length === 0 || state.length - n < state.highWaterMark) { + doRead = true; + debug('length less than watermark', doRead); + } + + // however, if we've ended, then there's no point, and if we're already + // reading, then it's unnecessary. + if (state.ended || state.reading) { + doRead = false; + debug('reading or ended', doRead); + } else if (doRead) { + debug('do read'); + state.reading = true; + state.sync = true; + // if the length is currently zero, then we *need* a readable event. + if (state.length === 0) state.needReadable = true; + // call internal read method + this._read(state.highWaterMark); + state.sync = false; + // If _read pushed data synchronously, then `reading` will be false, + // and we need to re-evaluate how much data we can return to the user. + if (!state.reading) n = howMuchToRead(nOrig, state); + } + + var ret; + if (n > 0) ret = fromList(n, state);else ret = null; + + if (ret === null) { + state.needReadable = true; + n = 0; + } else { + state.length -= n; + } + + if (state.length === 0) { + // If we have nothing in the buffer, then we want to know + // as soon as we *do* get something into the buffer. + if (!state.ended) state.needReadable = true; + + // If we tried to read() past the EOF, then emit end on the next tick. + if (nOrig !== n && state.ended) endReadable(this); + } + + if (ret !== null) this.emit('data', ret); + + return ret; +}; + +function onEofChunk(stream, state) { + if (state.ended) return; + if (state.decoder) { + var chunk = state.decoder.end(); + if (chunk && chunk.length) { + state.buffer.push(chunk); + state.length += state.objectMode ? 1 : chunk.length; + } + } + state.ended = true; + + // emit 'readable' now to make sure it gets picked up. + emitReadable(stream); +} + +// Don't emit readable right away in sync mode, because this can trigger +// another read() call => stack overflow. This way, it might trigger +// a nextTick recursion warning, but that's not so bad. +function emitReadable(stream) { + var state = stream._readableState; + state.needReadable = false; + if (!state.emittedReadable) { + debug('emitReadable', state.flowing); + state.emittedReadable = true; + if (state.sync) pna.nextTick(emitReadable_, stream);else emitReadable_(stream); + } +} + +function emitReadable_(stream) { + debug('emit readable'); + stream.emit('readable'); + flow(stream); +} + +// at this point, the user has presumably seen the 'readable' event, +// and called read() to consume some data. that may have triggered +// in turn another _read(n) call, in which case reading = true if +// it's in progress. +// However, if we're not ended, or reading, and the length < hwm, +// then go ahead and try to read some more preemptively. +function maybeReadMore(stream, state) { + if (!state.readingMore) { + state.readingMore = true; + pna.nextTick(maybeReadMore_, stream, state); + } +} + +function maybeReadMore_(stream, state) { + var len = state.length; + while (!state.reading && !state.flowing && !state.ended && state.length < state.highWaterMark) { + debug('maybeReadMore read 0'); + stream.read(0); + if (len === state.length) + // didn't get any data, stop spinning. + break;else len = state.length; + } + state.readingMore = false; +} + +// abstract method. to be overridden in specific implementation classes. +// call cb(er, data) where data is <= n in length. +// for virtual (non-string, non-buffer) streams, "length" is somewhat +// arbitrary, and perhaps not very meaningful. +Readable.prototype._read = function (n) { + this.emit('error', new Error('_read() is not implemented')); +}; + +Readable.prototype.pipe = function (dest, pipeOpts) { + var src = this; + var state = this._readableState; + + switch (state.pipesCount) { + case 0: + state.pipes = dest; + break; + case 1: + state.pipes = [state.pipes, dest]; + break; + default: + state.pipes.push(dest); + break; + } + state.pipesCount += 1; + debug('pipe count=%d opts=%j', state.pipesCount, pipeOpts); + + var doEnd = (!pipeOpts || pipeOpts.end !== false) && dest !== process.stdout && dest !== process.stderr; + + var endFn = doEnd ? onend : unpipe; + if (state.endEmitted) pna.nextTick(endFn);else src.once('end', endFn); + + dest.on('unpipe', onunpipe); + function onunpipe(readable, unpipeInfo) { + debug('onunpipe'); + if (readable === src) { + if (unpipeInfo && unpipeInfo.hasUnpiped === false) { + unpipeInfo.hasUnpiped = true; + cleanup(); + } + } + } + + function onend() { + debug('onend'); + dest.end(); + } + + // when the dest drains, it reduces the awaitDrain counter + // on the source. This would be more elegant with a .once() + // handler in flow(), but adding and removing repeatedly is + // too slow. + var ondrain = pipeOnDrain(src); + dest.on('drain', ondrain); + + var cleanedUp = false; + function cleanup() { + debug('cleanup'); + // cleanup event handlers once the pipe is broken + dest.removeListener('close', onclose); + dest.removeListener('finish', onfinish); + dest.removeListener('drain', ondrain); + dest.removeListener('error', onerror); + dest.removeListener('unpipe', onunpipe); + src.removeListener('end', onend); + src.removeListener('end', unpipe); + src.removeListener('data', ondata); + + cleanedUp = true; + + // if the reader is waiting for a drain event from this + // specific writer, then it would cause it to never start + // flowing again. + // So, if this is awaiting a drain, then we just call it now. + // If we don't know, then assume that we are waiting for one. + if (state.awaitDrain && (!dest._writableState || dest._writableState.needDrain)) ondrain(); + } + + // If the user pushes more data while we're writing to dest then we'll end up + // in ondata again. However, we only want to increase awaitDrain once because + // dest will only emit one 'drain' event for the multiple writes. + // => Introduce a guard on increasing awaitDrain. + var increasedAwaitDrain = false; + src.on('data', ondata); + function ondata(chunk) { + debug('ondata'); + increasedAwaitDrain = false; + var ret = dest.write(chunk); + if (false === ret && !increasedAwaitDrain) { + // If the user unpiped during `dest.write()`, it is possible + // to get stuck in a permanently paused state if that write + // also returned false. + // => Check whether `dest` is still a piping destination. + if ((state.pipesCount === 1 && state.pipes === dest || state.pipesCount > 1 && indexOf(state.pipes, dest) !== -1) && !cleanedUp) { + debug('false write response, pause', src._readableState.awaitDrain); + src._readableState.awaitDrain++; + increasedAwaitDrain = true; + } + src.pause(); + } + } + + // if the dest has an error, then stop piping into it. + // however, don't suppress the throwing behavior for this. + function onerror(er) { + debug('onerror', er); + unpipe(); + dest.removeListener('error', onerror); + if (EElistenerCount(dest, 'error') === 0) dest.emit('error', er); + } + + // Make sure our error handler is attached before userland ones. + prependListener(dest, 'error', onerror); + + // Both close and finish should trigger unpipe, but only once. + function onclose() { + dest.removeListener('finish', onfinish); + unpipe(); + } + dest.once('close', onclose); + function onfinish() { + debug('onfinish'); + dest.removeListener('close', onclose); + unpipe(); + } + dest.once('finish', onfinish); + + function unpipe() { + debug('unpipe'); + src.unpipe(dest); + } + + // tell the dest that it's being piped to + dest.emit('pipe', src); + + // start the flow if it hasn't been started already. + if (!state.flowing) { + debug('pipe resume'); + src.resume(); + } + + return dest; +}; + +function pipeOnDrain(src) { + return function () { + var state = src._readableState; + debug('pipeOnDrain', state.awaitDrain); + if (state.awaitDrain) state.awaitDrain--; + if (state.awaitDrain === 0 && EElistenerCount(src, 'data')) { + state.flowing = true; + flow(src); + } + }; +} + +Readable.prototype.unpipe = function (dest) { + var state = this._readableState; + var unpipeInfo = { hasUnpiped: false }; + + // if we're not piping anywhere, then do nothing. + if (state.pipesCount === 0) return this; + + // just one destination. most common case. + if (state.pipesCount === 1) { + // passed in one, but it's not the right one. + if (dest && dest !== state.pipes) return this; + + if (!dest) dest = state.pipes; + + // got a match. + state.pipes = null; + state.pipesCount = 0; + state.flowing = false; + if (dest) dest.emit('unpipe', this, unpipeInfo); + return this; + } + + // slow case. multiple pipe destinations. + + if (!dest) { + // remove all. + var dests = state.pipes; + var len = state.pipesCount; + state.pipes = null; + state.pipesCount = 0; + state.flowing = false; + + for (var i = 0; i < len; i++) { + dests[i].emit('unpipe', this, unpipeInfo); + }return this; + } + + // try to find the right one. + var index = indexOf(state.pipes, dest); + if (index === -1) return this; + + state.pipes.splice(index, 1); + state.pipesCount -= 1; + if (state.pipesCount === 1) state.pipes = state.pipes[0]; + + dest.emit('unpipe', this, unpipeInfo); + + return this; +}; + +// set up data events if they are asked for +// Ensure readable listeners eventually get something +Readable.prototype.on = function (ev, fn) { + var res = Stream.prototype.on.call(this, ev, fn); + + if (ev === 'data') { + // Start flowing on next tick if stream isn't explicitly paused + if (this._readableState.flowing !== false) this.resume(); + } else if (ev === 'readable') { + var state = this._readableState; + if (!state.endEmitted && !state.readableListening) { + state.readableListening = state.needReadable = true; + state.emittedReadable = false; + if (!state.reading) { + pna.nextTick(nReadingNextTick, this); + } else if (state.length) { + emitReadable(this); + } + } + } + + return res; +}; +Readable.prototype.addListener = Readable.prototype.on; + +function nReadingNextTick(self) { + debug('readable nexttick read 0'); + self.read(0); +} + +// pause() and resume() are remnants of the legacy readable stream API +// If the user uses them, then switch into old mode. +Readable.prototype.resume = function () { + var state = this._readableState; + if (!state.flowing) { + debug('resume'); + state.flowing = true; + resume(this, state); + } + return this; +}; + +function resume(stream, state) { + if (!state.resumeScheduled) { + state.resumeScheduled = true; + pna.nextTick(resume_, stream, state); + } +} + +function resume_(stream, state) { + if (!state.reading) { + debug('resume read 0'); + stream.read(0); + } + + state.resumeScheduled = false; + state.awaitDrain = 0; + stream.emit('resume'); + flow(stream); + if (state.flowing && !state.reading) stream.read(0); +} + +Readable.prototype.pause = function () { + debug('call pause flowing=%j', this._readableState.flowing); + if (false !== this._readableState.flowing) { + debug('pause'); + this._readableState.flowing = false; + this.emit('pause'); + } + return this; +}; + +function flow(stream) { + var state = stream._readableState; + debug('flow', state.flowing); + while (state.flowing && stream.read() !== null) {} +} + +// wrap an old-style stream as the async data source. +// This is *not* part of the readable stream interface. +// It is an ugly unfortunate mess of history. +Readable.prototype.wrap = function (stream) { + var _this = this; + + var state = this._readableState; + var paused = false; + + stream.on('end', function () { + debug('wrapped end'); + if (state.decoder && !state.ended) { + var chunk = state.decoder.end(); + if (chunk && chunk.length) _this.push(chunk); + } + + _this.push(null); + }); + + stream.on('data', function (chunk) { + debug('wrapped data'); + if (state.decoder) chunk = state.decoder.write(chunk); + + // don't skip over falsy values in objectMode + if (state.objectMode && (chunk === null || chunk === undefined)) return;else if (!state.objectMode && (!chunk || !chunk.length)) return; + + var ret = _this.push(chunk); + if (!ret) { + paused = true; + stream.pause(); + } + }); + + // proxy all the other methods. + // important when wrapping filters and duplexes. + for (var i in stream) { + if (this[i] === undefined && typeof stream[i] === 'function') { + this[i] = function (method) { + return function () { + return stream[method].apply(stream, arguments); + }; + }(i); + } + } + + // proxy certain important events. + for (var n = 0; n < kProxyEvents.length; n++) { + stream.on(kProxyEvents[n], this.emit.bind(this, kProxyEvents[n])); + } + + // when we try to consume some more bytes, simply unpause the + // underlying stream. + this._read = function (n) { + debug('wrapped _read', n); + if (paused) { + paused = false; + stream.resume(); + } + }; + + return this; +}; + +Object.defineProperty(Readable.prototype, 'readableHighWaterMark', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function () { + return this._readableState.highWaterMark; + } +}); + +// exposed for testing purposes only. +Readable._fromList = fromList; + +// Pluck off n bytes from an array of buffers. +// Length is the combined lengths of all the buffers in the list. +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function fromList(n, state) { + // nothing buffered + if (state.length === 0) return null; + + var ret; + if (state.objectMode) ret = state.buffer.shift();else if (!n || n >= state.length) { + // read it all, truncate the list + if (state.decoder) ret = state.buffer.join('');else if (state.buffer.length === 1) ret = state.buffer.head.data;else ret = state.buffer.concat(state.length); + state.buffer.clear(); + } else { + // read part of list + ret = fromListPartial(n, state.buffer, state.decoder); + } + + return ret; +} + +// Extracts only enough buffered data to satisfy the amount requested. +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function fromListPartial(n, list, hasStrings) { + var ret; + if (n < list.head.data.length) { + // slice is the same for buffers and strings + ret = list.head.data.slice(0, n); + list.head.data = list.head.data.slice(n); + } else if (n === list.head.data.length) { + // first chunk is a perfect match + ret = list.shift(); + } else { + // result spans more than one buffer + ret = hasStrings ? copyFromBufferString(n, list) : copyFromBuffer(n, list); + } + return ret; +} + +// Copies a specified amount of characters from the list of buffered data +// chunks. +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function copyFromBufferString(n, list) { + var p = list.head; + var c = 1; + var ret = p.data; + n -= ret.length; + while (p = p.next) { + var str = p.data; + var nb = n > str.length ? str.length : n; + if (nb === str.length) ret += str;else ret += str.slice(0, n); + n -= nb; + if (n === 0) { + if (nb === str.length) { + ++c; + if (p.next) list.head = p.next;else list.head = list.tail = null; + } else { + list.head = p; + p.data = str.slice(nb); + } + break; + } + ++c; + } + list.length -= c; + return ret; +} + +// Copies a specified amount of bytes from the list of buffered data chunks. +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function copyFromBuffer(n, list) { + var ret = Buffer.allocUnsafe(n); + var p = list.head; + var c = 1; + p.data.copy(ret); + n -= p.data.length; + while (p = p.next) { + var buf = p.data; + var nb = n > buf.length ? buf.length : n; + buf.copy(ret, ret.length - n, 0, nb); + n -= nb; + if (n === 0) { + if (nb === buf.length) { + ++c; + if (p.next) list.head = p.next;else list.head = list.tail = null; + } else { + list.head = p; + p.data = buf.slice(nb); + } + break; + } + ++c; + } + list.length -= c; + return ret; +} + +function endReadable(stream) { + var state = stream._readableState; + + // If we get here before consuming all the bytes, then that is a + // bug in node. Should never happen. + if (state.length > 0) throw new Error('"endReadable()" called on non-empty stream'); + + if (!state.endEmitted) { + state.ended = true; + pna.nextTick(endReadableNT, state, stream); + } +} + +function endReadableNT(state, stream) { + // Check that we didn't get one last unshift. + if (!state.endEmitted && state.length === 0) { + state.endEmitted = true; + stream.readable = false; + stream.emit('end'); + } +} + +function indexOf(xs, x) { + for (var i = 0, l = xs.length; i < l; i++) { + if (xs[i] === x) return i; + } + return -1; +} +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./_stream_duplex":71,"./internal/streams/BufferList":76,"./internal/streams/destroy":77,"./internal/streams/stream":78,"_process":69,"core-util-is":44,"events":50,"inherits":56,"isarray":58,"process-nextick-args":68,"safe-buffer":83,"string_decoder/":85,"util":40}],74:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// a transform stream is a readable/writable stream where you do +// something with the data. Sometimes it's called a "filter", +// but that's not a great name for it, since that implies a thing where +// some bits pass through, and others are simply ignored. (That would +// be a valid example of a transform, of course.) +// +// While the output is causally related to the input, it's not a +// necessarily symmetric or synchronous transformation. For example, +// a zlib stream might take multiple plain-text writes(), and then +// emit a single compressed chunk some time in the future. +// +// Here's how this works: +// +// The Transform stream has all the aspects of the readable and writable +// stream classes. When you write(chunk), that calls _write(chunk,cb) +// internally, and returns false if there's a lot of pending writes +// buffered up. When you call read(), that calls _read(n) until +// there's enough pending readable data buffered up. +// +// In a transform stream, the written data is placed in a buffer. When +// _read(n) is called, it transforms the queued up data, calling the +// buffered _write cb's as it consumes chunks. If consuming a single +// written chunk would result in multiple output chunks, then the first +// outputted bit calls the readcb, and subsequent chunks just go into +// the read buffer, and will cause it to emit 'readable' if necessary. +// +// This way, back-pressure is actually determined by the reading side, +// since _read has to be called to start processing a new chunk. However, +// a pathological inflate type of transform can cause excessive buffering +// here. For example, imagine a stream where every byte of input is +// interpreted as an integer from 0-255, and then results in that many +// bytes of output. Writing the 4 bytes {ff,ff,ff,ff} would result in +// 1kb of data being output. In this case, you could write a very small +// amount of input, and end up with a very large amount of output. In +// such a pathological inflating mechanism, there'd be no way to tell +// the system to stop doing the transform. A single 4MB write could +// cause the system to run out of memory. +// +// However, even in such a pathological case, only a single written chunk +// would be consumed, and then the rest would wait (un-transformed) until +// the results of the previous transformed chunk were consumed. + +'use strict'; + +module.exports = Transform; + +var Duplex = require('./_stream_duplex'); + +/**/ +var util = require('core-util-is'); +util.inherits = require('inherits'); +/**/ + +util.inherits(Transform, Duplex); + +function afterTransform(er, data) { + var ts = this._transformState; + ts.transforming = false; + + var cb = ts.writecb; + + if (!cb) { + return this.emit('error', new Error('write callback called multiple times')); + } + + ts.writechunk = null; + ts.writecb = null; + + if (data != null) // single equals check for both `null` and `undefined` + this.push(data); + + cb(er); + + var rs = this._readableState; + rs.reading = false; + if (rs.needReadable || rs.length < rs.highWaterMark) { + this._read(rs.highWaterMark); + } +} + +function Transform(options) { + if (!(this instanceof Transform)) return new Transform(options); + + Duplex.call(this, options); + + this._transformState = { + afterTransform: afterTransform.bind(this), + needTransform: false, + transforming: false, + writecb: null, + writechunk: null, + writeencoding: null + }; + + // start out asking for a readable event once data is transformed. + this._readableState.needReadable = true; + + // we have implemented the _read method, and done the other things + // that Readable wants before the first _read call, so unset the + // sync guard flag. + this._readableState.sync = false; + + if (options) { + if (typeof options.transform === 'function') this._transform = options.transform; + + if (typeof options.flush === 'function') this._flush = options.flush; + } + + // When the writable side finishes, then flush out anything remaining. + this.on('prefinish', prefinish); +} + +function prefinish() { + var _this = this; + + if (typeof this._flush === 'function') { + this._flush(function (er, data) { + done(_this, er, data); + }); + } else { + done(this, null, null); + } +} + +Transform.prototype.push = function (chunk, encoding) { + this._transformState.needTransform = false; + return Duplex.prototype.push.call(this, chunk, encoding); +}; + +// This is the part where you do stuff! +// override this function in implementation classes. +// 'chunk' is an input chunk. +// +// Call `push(newChunk)` to pass along transformed output +// to the readable side. You may call 'push' zero or more times. +// +// Call `cb(err)` when you are done with this chunk. If you pass +// an error, then that'll put the hurt on the whole operation. If you +// never call cb(), then you'll never get another chunk. +Transform.prototype._transform = function (chunk, encoding, cb) { + throw new Error('_transform() is not implemented'); +}; + +Transform.prototype._write = function (chunk, encoding, cb) { + var ts = this._transformState; + ts.writecb = cb; + ts.writechunk = chunk; + ts.writeencoding = encoding; + if (!ts.transforming) { + var rs = this._readableState; + if (ts.needTransform || rs.needReadable || rs.length < rs.highWaterMark) this._read(rs.highWaterMark); + } +}; + +// Doesn't matter what the args are here. +// _transform does all the work. +// That we got here means that the readable side wants more data. +Transform.prototype._read = function (n) { + var ts = this._transformState; + + if (ts.writechunk !== null && ts.writecb && !ts.transforming) { + ts.transforming = true; + this._transform(ts.writechunk, ts.writeencoding, ts.afterTransform); + } else { + // mark that we need a transform, so that any data that comes in + // will get processed, now that we've asked for it. + ts.needTransform = true; + } +}; + +Transform.prototype._destroy = function (err, cb) { + var _this2 = this; + + Duplex.prototype._destroy.call(this, err, function (err2) { + cb(err2); + _this2.emit('close'); + }); +}; + +function done(stream, er, data) { + if (er) return stream.emit('error', er); + + if (data != null) // single equals check for both `null` and `undefined` + stream.push(data); + + // if there's nothing in the write buffer, then that means + // that nothing more will ever be provided + if (stream._writableState.length) throw new Error('Calling transform done when ws.length != 0'); + + if (stream._transformState.transforming) throw new Error('Calling transform done when still transforming'); + + return stream.push(null); +} +},{"./_stream_duplex":71,"core-util-is":44,"inherits":56}],75:[function(require,module,exports){ +(function (process,global,setImmediate){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// A bit simpler than readable streams. +// Implement an async ._write(chunk, encoding, cb), and it'll handle all +// the drain event emission and buffering. + +'use strict'; + +/**/ + +var pna = require('process-nextick-args'); +/**/ + +module.exports = Writable; + +/* */ +function WriteReq(chunk, encoding, cb) { + this.chunk = chunk; + this.encoding = encoding; + this.callback = cb; + this.next = null; +} + +// It seems a linked list but it is not +// there will be only 2 of these for each stream +function CorkedRequest(state) { + var _this = this; + + this.next = null; + this.entry = null; + this.finish = function () { + onCorkedFinish(_this, state); + }; +} +/* */ + +/**/ +var asyncWrite = !process.browser && ['v0.10', 'v0.9.'].indexOf(process.version.slice(0, 5)) > -1 ? setImmediate : pna.nextTick; +/**/ + +/**/ +var Duplex; +/**/ + +Writable.WritableState = WritableState; + +/**/ +var util = require('core-util-is'); +util.inherits = require('inherits'); +/**/ + +/**/ +var internalUtil = { + deprecate: require('util-deprecate') +}; +/**/ + +/**/ +var Stream = require('./internal/streams/stream'); +/**/ + +/**/ + +var Buffer = require('safe-buffer').Buffer; +var OurUint8Array = global.Uint8Array || function () {}; +function _uint8ArrayToBuffer(chunk) { + return Buffer.from(chunk); +} +function _isUint8Array(obj) { + return Buffer.isBuffer(obj) || obj instanceof OurUint8Array; +} + +/**/ + +var destroyImpl = require('./internal/streams/destroy'); + +util.inherits(Writable, Stream); + +function nop() {} + +function WritableState(options, stream) { + Duplex = Duplex || require('./_stream_duplex'); + + options = options || {}; + + // Duplex streams are both readable and writable, but share + // the same options object. + // However, some cases require setting options to different + // values for the readable and the writable sides of the duplex stream. + // These options can be provided separately as readableXXX and writableXXX. + var isDuplex = stream instanceof Duplex; + + // object stream flag to indicate whether or not this stream + // contains buffers or objects. + this.objectMode = !!options.objectMode; + + if (isDuplex) this.objectMode = this.objectMode || !!options.writableObjectMode; + + // the point at which write() starts returning false + // Note: 0 is a valid value, means that we always return false if + // the entire buffer is not flushed immediately on write() + var hwm = options.highWaterMark; + var writableHwm = options.writableHighWaterMark; + var defaultHwm = this.objectMode ? 16 : 16 * 1024; + + if (hwm || hwm === 0) this.highWaterMark = hwm;else if (isDuplex && (writableHwm || writableHwm === 0)) this.highWaterMark = writableHwm;else this.highWaterMark = defaultHwm; + + // cast to ints. + this.highWaterMark = Math.floor(this.highWaterMark); + + // if _final has been called + this.finalCalled = false; + + // drain event flag. + this.needDrain = false; + // at the start of calling end() + this.ending = false; + // when end() has been called, and returned + this.ended = false; + // when 'finish' is emitted + this.finished = false; + + // has it been destroyed + this.destroyed = false; + + // should we decode strings into buffers before passing to _write? + // this is here so that some node-core streams can optimize string + // handling at a lower level. + var noDecode = options.decodeStrings === false; + this.decodeStrings = !noDecode; + + // Crypto is kind of old and crusty. Historically, its default string + // encoding is 'binary' so we have to make this configurable. + // Everything else in the universe uses 'utf8', though. + this.defaultEncoding = options.defaultEncoding || 'utf8'; + + // not an actual buffer we keep track of, but a measurement + // of how much we're waiting to get pushed to some underlying + // socket or file. + this.length = 0; + + // a flag to see when we're in the middle of a write. + this.writing = false; + + // when true all writes will be buffered until .uncork() call + this.corked = 0; + + // a flag to be able to tell if the onwrite cb is called immediately, + // or on a later tick. We set this to true at first, because any + // actions that shouldn't happen until "later" should generally also + // not happen before the first write call. + this.sync = true; + + // a flag to know if we're processing previously buffered items, which + // may call the _write() callback in the same tick, so that we don't + // end up in an overlapped onwrite situation. + this.bufferProcessing = false; + + // the callback that's passed to _write(chunk,cb) + this.onwrite = function (er) { + onwrite(stream, er); + }; + + // the callback that the user supplies to write(chunk,encoding,cb) + this.writecb = null; + + // the amount that is being written when _write is called. + this.writelen = 0; + + this.bufferedRequest = null; + this.lastBufferedRequest = null; + + // number of pending user-supplied write callbacks + // this must be 0 before 'finish' can be emitted + this.pendingcb = 0; + + // emit prefinish if the only thing we're waiting for is _write cbs + // This is relevant for synchronous Transform streams + this.prefinished = false; + + // True if the error was already emitted and should not be thrown again + this.errorEmitted = false; + + // count buffered requests + this.bufferedRequestCount = 0; + + // allocate the first CorkedRequest, there is always + // one allocated and free to use, and we maintain at most two + this.corkedRequestsFree = new CorkedRequest(this); +} + +WritableState.prototype.getBuffer = function getBuffer() { + var current = this.bufferedRequest; + var out = []; + while (current) { + out.push(current); + current = current.next; + } + return out; +}; + +(function () { + try { + Object.defineProperty(WritableState.prototype, 'buffer', { + get: internalUtil.deprecate(function () { + return this.getBuffer(); + }, '_writableState.buffer is deprecated. Use _writableState.getBuffer ' + 'instead.', 'DEP0003') + }); + } catch (_) {} })(); -})(); \ No newline at end of file + +// Test _writableState for inheritance to account for Duplex streams, +// whose prototype chain only points to Readable. +var realHasInstance; +if (typeof Symbol === 'function' && Symbol.hasInstance && typeof Function.prototype[Symbol.hasInstance] === 'function') { + realHasInstance = Function.prototype[Symbol.hasInstance]; + Object.defineProperty(Writable, Symbol.hasInstance, { + value: function (object) { + if (realHasInstance.call(this, object)) return true; + if (this !== Writable) return false; + + return object && object._writableState instanceof WritableState; + } + }); +} else { + realHasInstance = function (object) { + return object instanceof this; + }; +} + +function Writable(options) { + Duplex = Duplex || require('./_stream_duplex'); + + // Writable ctor is applied to Duplexes, too. + // `realHasInstance` is necessary because using plain `instanceof` + // would return false, as no `_writableState` property is attached. + + // Trying to use the custom `instanceof` for Writable here will also break the + // Node.js LazyTransform implementation, which has a non-trivial getter for + // `_writableState` that would lead to infinite recursion. + if (!realHasInstance.call(Writable, this) && !(this instanceof Duplex)) { + return new Writable(options); + } + + this._writableState = new WritableState(options, this); + + // legacy. + this.writable = true; + + if (options) { + if (typeof options.write === 'function') this._write = options.write; + + if (typeof options.writev === 'function') this._writev = options.writev; + + if (typeof options.destroy === 'function') this._destroy = options.destroy; + + if (typeof options.final === 'function') this._final = options.final; + } + + Stream.call(this); +} + +// Otherwise people can pipe Writable streams, which is just wrong. +Writable.prototype.pipe = function () { + this.emit('error', new Error('Cannot pipe, not readable')); +}; + +function writeAfterEnd(stream, cb) { + var er = new Error('write after end'); + // TODO: defer error events consistently everywhere, not just the cb + stream.emit('error', er); + pna.nextTick(cb, er); +} + +// Checks that a user-supplied chunk is valid, especially for the particular +// mode the stream is in. Currently this means that `null` is never accepted +// and undefined/non-string values are only allowed in object mode. +function validChunk(stream, state, chunk, cb) { + var valid = true; + var er = false; + + if (chunk === null) { + er = new TypeError('May not write null values to stream'); + } else if (typeof chunk !== 'string' && chunk !== undefined && !state.objectMode) { + er = new TypeError('Invalid non-string/buffer chunk'); + } + if (er) { + stream.emit('error', er); + pna.nextTick(cb, er); + valid = false; + } + return valid; +} + +Writable.prototype.write = function (chunk, encoding, cb) { + var state = this._writableState; + var ret = false; + var isBuf = !state.objectMode && _isUint8Array(chunk); + + if (isBuf && !Buffer.isBuffer(chunk)) { + chunk = _uint8ArrayToBuffer(chunk); + } + + if (typeof encoding === 'function') { + cb = encoding; + encoding = null; + } + + if (isBuf) encoding = 'buffer';else if (!encoding) encoding = state.defaultEncoding; + + if (typeof cb !== 'function') cb = nop; + + if (state.ended) writeAfterEnd(this, cb);else if (isBuf || validChunk(this, state, chunk, cb)) { + state.pendingcb++; + ret = writeOrBuffer(this, state, isBuf, chunk, encoding, cb); + } + + return ret; +}; + +Writable.prototype.cork = function () { + var state = this._writableState; + + state.corked++; +}; + +Writable.prototype.uncork = function () { + var state = this._writableState; + + if (state.corked) { + state.corked--; + + if (!state.writing && !state.corked && !state.finished && !state.bufferProcessing && state.bufferedRequest) clearBuffer(this, state); + } +}; + +Writable.prototype.setDefaultEncoding = function setDefaultEncoding(encoding) { + // node::ParseEncoding() requires lower case. + if (typeof encoding === 'string') encoding = encoding.toLowerCase(); + if (!(['hex', 'utf8', 'utf-8', 'ascii', 'binary', 'base64', 'ucs2', 'ucs-2', 'utf16le', 'utf-16le', 'raw'].indexOf((encoding + '').toLowerCase()) > -1)) throw new TypeError('Unknown encoding: ' + encoding); + this._writableState.defaultEncoding = encoding; + return this; +}; + +function decodeChunk(state, chunk, encoding) { + if (!state.objectMode && state.decodeStrings !== false && typeof chunk === 'string') { + chunk = Buffer.from(chunk, encoding); + } + return chunk; +} + +Object.defineProperty(Writable.prototype, 'writableHighWaterMark', { + // making it explicit this property is not enumerable + // because otherwise some prototype manipulation in + // userland will fail + enumerable: false, + get: function () { + return this._writableState.highWaterMark; + } +}); + +// if we're already writing something, then just put this +// in the queue, and wait our turn. Otherwise, call _write +// If we return false, then we need a drain event, so set that flag. +function writeOrBuffer(stream, state, isBuf, chunk, encoding, cb) { + if (!isBuf) { + var newChunk = decodeChunk(state, chunk, encoding); + if (chunk !== newChunk) { + isBuf = true; + encoding = 'buffer'; + chunk = newChunk; + } + } + var len = state.objectMode ? 1 : chunk.length; + + state.length += len; + + var ret = state.length < state.highWaterMark; + // we must ensure that previous needDrain will not be reset to false. + if (!ret) state.needDrain = true; + + if (state.writing || state.corked) { + var last = state.lastBufferedRequest; + state.lastBufferedRequest = { + chunk: chunk, + encoding: encoding, + isBuf: isBuf, + callback: cb, + next: null + }; + if (last) { + last.next = state.lastBufferedRequest; + } else { + state.bufferedRequest = state.lastBufferedRequest; + } + state.bufferedRequestCount += 1; + } else { + doWrite(stream, state, false, len, chunk, encoding, cb); + } + + return ret; +} + +function doWrite(stream, state, writev, len, chunk, encoding, cb) { + state.writelen = len; + state.writecb = cb; + state.writing = true; + state.sync = true; + if (writev) stream._writev(chunk, state.onwrite);else stream._write(chunk, encoding, state.onwrite); + state.sync = false; +} + +function onwriteError(stream, state, sync, er, cb) { + --state.pendingcb; + + if (sync) { + // defer the callback if we are being called synchronously + // to avoid piling up things on the stack + pna.nextTick(cb, er); + // this can emit finish, and it will always happen + // after error + pna.nextTick(finishMaybe, stream, state); + stream._writableState.errorEmitted = true; + stream.emit('error', er); + } else { + // the caller expect this to happen before if + // it is async + cb(er); + stream._writableState.errorEmitted = true; + stream.emit('error', er); + // this can emit finish, but finish must + // always follow error + finishMaybe(stream, state); + } +} + +function onwriteStateUpdate(state) { + state.writing = false; + state.writecb = null; + state.length -= state.writelen; + state.writelen = 0; +} + +function onwrite(stream, er) { + var state = stream._writableState; + var sync = state.sync; + var cb = state.writecb; + + onwriteStateUpdate(state); + + if (er) onwriteError(stream, state, sync, er, cb);else { + // Check if we're actually ready to finish, but don't emit yet + var finished = needFinish(state); + + if (!finished && !state.corked && !state.bufferProcessing && state.bufferedRequest) { + clearBuffer(stream, state); + } + + if (sync) { + /**/ + asyncWrite(afterWrite, stream, state, finished, cb); + /**/ + } else { + afterWrite(stream, state, finished, cb); + } + } +} + +function afterWrite(stream, state, finished, cb) { + if (!finished) onwriteDrain(stream, state); + state.pendingcb--; + cb(); + finishMaybe(stream, state); +} + +// Must force callback to be called on nextTick, so that we don't +// emit 'drain' before the write() consumer gets the 'false' return +// value, and has a chance to attach a 'drain' listener. +function onwriteDrain(stream, state) { + if (state.length === 0 && state.needDrain) { + state.needDrain = false; + stream.emit('drain'); + } +} + +// if there's something in the buffer waiting, then process it +function clearBuffer(stream, state) { + state.bufferProcessing = true; + var entry = state.bufferedRequest; + + if (stream._writev && entry && entry.next) { + // Fast case, write everything using _writev() + var l = state.bufferedRequestCount; + var buffer = new Array(l); + var holder = state.corkedRequestsFree; + holder.entry = entry; + + var count = 0; + var allBuffers = true; + while (entry) { + buffer[count] = entry; + if (!entry.isBuf) allBuffers = false; + entry = entry.next; + count += 1; + } + buffer.allBuffers = allBuffers; + + doWrite(stream, state, true, state.length, buffer, '', holder.finish); + + // doWrite is almost always async, defer these to save a bit of time + // as the hot path ends with doWrite + state.pendingcb++; + state.lastBufferedRequest = null; + if (holder.next) { + state.corkedRequestsFree = holder.next; + holder.next = null; + } else { + state.corkedRequestsFree = new CorkedRequest(state); + } + state.bufferedRequestCount = 0; + } else { + // Slow case, write chunks one-by-one + while (entry) { + var chunk = entry.chunk; + var encoding = entry.encoding; + var cb = entry.callback; + var len = state.objectMode ? 1 : chunk.length; + + doWrite(stream, state, false, len, chunk, encoding, cb); + entry = entry.next; + state.bufferedRequestCount--; + // if we didn't call the onwrite immediately, then + // it means that we need to wait until it does. + // also, that means that the chunk and cb are currently + // being processed, so move the buffer counter past them. + if (state.writing) { + break; + } + } + + if (entry === null) state.lastBufferedRequest = null; + } + + state.bufferedRequest = entry; + state.bufferProcessing = false; +} + +Writable.prototype._write = function (chunk, encoding, cb) { + cb(new Error('_write() is not implemented')); +}; + +Writable.prototype._writev = null; + +Writable.prototype.end = function (chunk, encoding, cb) { + var state = this._writableState; + + if (typeof chunk === 'function') { + cb = chunk; + chunk = null; + encoding = null; + } else if (typeof encoding === 'function') { + cb = encoding; + encoding = null; + } + + if (chunk !== null && chunk !== undefined) this.write(chunk, encoding); + + // .end() fully uncorks + if (state.corked) { + state.corked = 1; + this.uncork(); + } + + // ignore unnecessary end() calls. + if (!state.ending && !state.finished) endWritable(this, state, cb); +}; + +function needFinish(state) { + return state.ending && state.length === 0 && state.bufferedRequest === null && !state.finished && !state.writing; +} +function callFinal(stream, state) { + stream._final(function (err) { + state.pendingcb--; + if (err) { + stream.emit('error', err); + } + state.prefinished = true; + stream.emit('prefinish'); + finishMaybe(stream, state); + }); +} +function prefinish(stream, state) { + if (!state.prefinished && !state.finalCalled) { + if (typeof stream._final === 'function') { + state.pendingcb++; + state.finalCalled = true; + pna.nextTick(callFinal, stream, state); + } else { + state.prefinished = true; + stream.emit('prefinish'); + } + } +} + +function finishMaybe(stream, state) { + var need = needFinish(state); + if (need) { + prefinish(stream, state); + if (state.pendingcb === 0) { + state.finished = true; + stream.emit('finish'); + } + } + return need; +} + +function endWritable(stream, state, cb) { + state.ending = true; + finishMaybe(stream, state); + if (cb) { + if (state.finished) pna.nextTick(cb);else stream.once('finish', cb); + } + state.ended = true; + stream.writable = false; +} + +function onCorkedFinish(corkReq, state, err) { + var entry = corkReq.entry; + corkReq.entry = null; + while (entry) { + var cb = entry.callback; + state.pendingcb--; + cb(err); + entry = entry.next; + } + if (state.corkedRequestsFree) { + state.corkedRequestsFree.next = corkReq; + } else { + state.corkedRequestsFree = corkReq; + } +} + +Object.defineProperty(Writable.prototype, 'destroyed', { + get: function () { + if (this._writableState === undefined) { + return false; + } + return this._writableState.destroyed; + }, + set: function (value) { + // we ignore the value if the stream + // has not been initialized yet + if (!this._writableState) { + return; + } + + // backward compatibility, the user is explicitly + // managing destroyed + this._writableState.destroyed = value; + } +}); + +Writable.prototype.destroy = destroyImpl.destroy; +Writable.prototype._undestroy = destroyImpl.undestroy; +Writable.prototype._destroy = function (err, cb) { + this.end(); + cb(err); +}; +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},require("timers").setImmediate) +},{"./_stream_duplex":71,"./internal/streams/destroy":77,"./internal/streams/stream":78,"_process":69,"core-util-is":44,"inherits":56,"process-nextick-args":68,"safe-buffer":83,"timers":86,"util-deprecate":87}],76:[function(require,module,exports){ +'use strict'; + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var Buffer = require('safe-buffer').Buffer; +var util = require('util'); + +function copyBuffer(src, target, offset) { + src.copy(target, offset); +} + +module.exports = function () { + function BufferList() { + _classCallCheck(this, BufferList); + + this.head = null; + this.tail = null; + this.length = 0; + } + + BufferList.prototype.push = function push(v) { + var entry = { data: v, next: null }; + if (this.length > 0) this.tail.next = entry;else this.head = entry; + this.tail = entry; + ++this.length; + }; + + BufferList.prototype.unshift = function unshift(v) { + var entry = { data: v, next: this.head }; + if (this.length === 0) this.tail = entry; + this.head = entry; + ++this.length; + }; + + BufferList.prototype.shift = function shift() { + if (this.length === 0) return; + var ret = this.head.data; + if (this.length === 1) this.head = this.tail = null;else this.head = this.head.next; + --this.length; + return ret; + }; + + BufferList.prototype.clear = function clear() { + this.head = this.tail = null; + this.length = 0; + }; + + BufferList.prototype.join = function join(s) { + if (this.length === 0) return ''; + var p = this.head; + var ret = '' + p.data; + while (p = p.next) { + ret += s + p.data; + }return ret; + }; + + BufferList.prototype.concat = function concat(n) { + if (this.length === 0) return Buffer.alloc(0); + if (this.length === 1) return this.head.data; + var ret = Buffer.allocUnsafe(n >>> 0); + var p = this.head; + var i = 0; + while (p) { + copyBuffer(p.data, ret, i); + i += p.data.length; + p = p.next; + } + return ret; + }; + + return BufferList; +}(); + +if (util && util.inspect && util.inspect.custom) { + module.exports.prototype[util.inspect.custom] = function () { + var obj = util.inspect({ length: this.length }); + return this.constructor.name + ' ' + obj; + }; +} +},{"safe-buffer":83,"util":40}],77:[function(require,module,exports){ +'use strict'; + +/**/ + +var pna = require('process-nextick-args'); +/**/ + +// undocumented cb() API, needed for core, not for public API +function destroy(err, cb) { + var _this = this; + + var readableDestroyed = this._readableState && this._readableState.destroyed; + var writableDestroyed = this._writableState && this._writableState.destroyed; + + if (readableDestroyed || writableDestroyed) { + if (cb) { + cb(err); + } else if (err && (!this._writableState || !this._writableState.errorEmitted)) { + pna.nextTick(emitErrorNT, this, err); + } + return this; + } + + // we set destroyed to true before firing error callbacks in order + // to make it re-entrance safe in case destroy() is called within callbacks + + if (this._readableState) { + this._readableState.destroyed = true; + } + + // if this is a duplex stream mark the writable part as destroyed as well + if (this._writableState) { + this._writableState.destroyed = true; + } + + this._destroy(err || null, function (err) { + if (!cb && err) { + pna.nextTick(emitErrorNT, _this, err); + if (_this._writableState) { + _this._writableState.errorEmitted = true; + } + } else if (cb) { + cb(err); + } + }); + + return this; +} + +function undestroy() { + if (this._readableState) { + this._readableState.destroyed = false; + this._readableState.reading = false; + this._readableState.ended = false; + this._readableState.endEmitted = false; + } + + if (this._writableState) { + this._writableState.destroyed = false; + this._writableState.ended = false; + this._writableState.ending = false; + this._writableState.finished = false; + this._writableState.errorEmitted = false; + } +} + +function emitErrorNT(self, err) { + self.emit('error', err); +} + +module.exports = { + destroy: destroy, + undestroy: undestroy +}; +},{"process-nextick-args":68}],78:[function(require,module,exports){ +module.exports = require('events').EventEmitter; + +},{"events":50}],79:[function(require,module,exports){ +module.exports = require('./readable').PassThrough + +},{"./readable":80}],80:[function(require,module,exports){ +exports = module.exports = require('./lib/_stream_readable.js'); +exports.Stream = exports; +exports.Readable = exports; +exports.Writable = require('./lib/_stream_writable.js'); +exports.Duplex = require('./lib/_stream_duplex.js'); +exports.Transform = require('./lib/_stream_transform.js'); +exports.PassThrough = require('./lib/_stream_passthrough.js'); + +},{"./lib/_stream_duplex.js":71,"./lib/_stream_passthrough.js":72,"./lib/_stream_readable.js":73,"./lib/_stream_transform.js":74,"./lib/_stream_writable.js":75}],81:[function(require,module,exports){ +module.exports = require('./readable').Transform + +},{"./readable":80}],82:[function(require,module,exports){ +module.exports = require('./lib/_stream_writable.js'); + +},{"./lib/_stream_writable.js":75}],83:[function(require,module,exports){ +/* eslint-disable node/no-deprecated-api */ +var buffer = require('buffer') +var Buffer = buffer.Buffer + +// alternative to using Object.keys for old browsers +function copyProps (src, dst) { + for (var key in src) { + dst[key] = src[key] + } +} +if (Buffer.from && Buffer.alloc && Buffer.allocUnsafe && Buffer.allocUnsafeSlow) { + module.exports = buffer +} else { + // Copy properties from require('buffer') + copyProps(buffer, exports) + exports.Buffer = SafeBuffer +} + +function SafeBuffer (arg, encodingOrOffset, length) { + return Buffer(arg, encodingOrOffset, length) +} + +// Copy static methods from Buffer +copyProps(Buffer, SafeBuffer) + +SafeBuffer.from = function (arg, encodingOrOffset, length) { + if (typeof arg === 'number') { + throw new TypeError('Argument must not be a number') + } + return Buffer(arg, encodingOrOffset, length) +} + +SafeBuffer.alloc = function (size, fill, encoding) { + if (typeof size !== 'number') { + throw new TypeError('Argument must be a number') + } + var buf = Buffer(size) + if (fill !== undefined) { + if (typeof encoding === 'string') { + buf.fill(fill, encoding) + } else { + buf.fill(fill) + } + } else { + buf.fill(0) + } + return buf +} + +SafeBuffer.allocUnsafe = function (size) { + if (typeof size !== 'number') { + throw new TypeError('Argument must be a number') + } + return Buffer(size) +} + +SafeBuffer.allocUnsafeSlow = function (size) { + if (typeof size !== 'number') { + throw new TypeError('Argument must be a number') + } + return buffer.SlowBuffer(size) +} + +},{"buffer":43}],84:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +module.exports = Stream; + +var EE = require('events').EventEmitter; +var inherits = require('inherits'); + +inherits(Stream, EE); +Stream.Readable = require('readable-stream/readable.js'); +Stream.Writable = require('readable-stream/writable.js'); +Stream.Duplex = require('readable-stream/duplex.js'); +Stream.Transform = require('readable-stream/transform.js'); +Stream.PassThrough = require('readable-stream/passthrough.js'); + +// Backwards-compat with node 0.4.x +Stream.Stream = Stream; + + + +// old-style streams. Note that the pipe method (the only relevant +// part of this class) is overridden in the Readable class. + +function Stream() { + EE.call(this); +} + +Stream.prototype.pipe = function(dest, options) { + var source = this; + + function ondata(chunk) { + if (dest.writable) { + if (false === dest.write(chunk) && source.pause) { + source.pause(); + } + } + } + + source.on('data', ondata); + + function ondrain() { + if (source.readable && source.resume) { + source.resume(); + } + } + + dest.on('drain', ondrain); + + // If the 'end' option is not supplied, dest.end() will be called when + // source gets the 'end' or 'close' events. Only dest.end() once. + if (!dest._isStdio && (!options || options.end !== false)) { + source.on('end', onend); + source.on('close', onclose); + } + + var didOnEnd = false; + function onend() { + if (didOnEnd) return; + didOnEnd = true; + + dest.end(); + } + + + function onclose() { + if (didOnEnd) return; + didOnEnd = true; + + if (typeof dest.destroy === 'function') dest.destroy(); + } + + // don't leave dangling pipes when there are errors. + function onerror(er) { + cleanup(); + if (EE.listenerCount(this, 'error') === 0) { + throw er; // Unhandled stream error in pipe. + } + } + + source.on('error', onerror); + dest.on('error', onerror); + + // remove all the event listeners that were added. + function cleanup() { + source.removeListener('data', ondata); + dest.removeListener('drain', ondrain); + + source.removeListener('end', onend); + source.removeListener('close', onclose); + + source.removeListener('error', onerror); + dest.removeListener('error', onerror); + + source.removeListener('end', cleanup); + source.removeListener('close', cleanup); + + dest.removeListener('close', cleanup); + } + + source.on('end', cleanup); + source.on('close', cleanup); + + dest.on('close', cleanup); + + dest.emit('pipe', source); + + // Allow for unix-like usage: A.pipe(B).pipe(C) + return dest; +}; + +},{"events":50,"inherits":56,"readable-stream/duplex.js":70,"readable-stream/passthrough.js":79,"readable-stream/readable.js":80,"readable-stream/transform.js":81,"readable-stream/writable.js":82}],85:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +/**/ + +var Buffer = require('safe-buffer').Buffer; +/**/ + +var isEncoding = Buffer.isEncoding || function (encoding) { + encoding = '' + encoding; + switch (encoding && encoding.toLowerCase()) { + case 'hex':case 'utf8':case 'utf-8':case 'ascii':case 'binary':case 'base64':case 'ucs2':case 'ucs-2':case 'utf16le':case 'utf-16le':case 'raw': + return true; + default: + return false; + } +}; + +function _normalizeEncoding(enc) { + if (!enc) return 'utf8'; + var retried; + while (true) { + switch (enc) { + case 'utf8': + case 'utf-8': + return 'utf8'; + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return 'utf16le'; + case 'latin1': + case 'binary': + return 'latin1'; + case 'base64': + case 'ascii': + case 'hex': + return enc; + default: + if (retried) return; // undefined + enc = ('' + enc).toLowerCase(); + retried = true; + } + } +}; + +// Do not cache `Buffer.isEncoding` when checking encoding names as some +// modules monkey-patch it to support additional encodings +function normalizeEncoding(enc) { + var nenc = _normalizeEncoding(enc); + if (typeof nenc !== 'string' && (Buffer.isEncoding === isEncoding || !isEncoding(enc))) throw new Error('Unknown encoding: ' + enc); + return nenc || enc; +} + +// StringDecoder provides an interface for efficiently splitting a series of +// buffers into a series of JS strings without breaking apart multi-byte +// characters. +exports.StringDecoder = StringDecoder; +function StringDecoder(encoding) { + this.encoding = normalizeEncoding(encoding); + var nb; + switch (this.encoding) { + case 'utf16le': + this.text = utf16Text; + this.end = utf16End; + nb = 4; + break; + case 'utf8': + this.fillLast = utf8FillLast; + nb = 4; + break; + case 'base64': + this.text = base64Text; + this.end = base64End; + nb = 3; + break; + default: + this.write = simpleWrite; + this.end = simpleEnd; + return; + } + this.lastNeed = 0; + this.lastTotal = 0; + this.lastChar = Buffer.allocUnsafe(nb); +} + +StringDecoder.prototype.write = function (buf) { + if (buf.length === 0) return ''; + var r; + var i; + if (this.lastNeed) { + r = this.fillLast(buf); + if (r === undefined) return ''; + i = this.lastNeed; + this.lastNeed = 0; + } else { + i = 0; + } + if (i < buf.length) return r ? r + this.text(buf, i) : this.text(buf, i); + return r || ''; +}; + +StringDecoder.prototype.end = utf8End; + +// Returns only complete characters in a Buffer +StringDecoder.prototype.text = utf8Text; + +// Attempts to complete a partial non-UTF-8 character using bytes from a Buffer +StringDecoder.prototype.fillLast = function (buf) { + if (this.lastNeed <= buf.length) { + buf.copy(this.lastChar, this.lastTotal - this.lastNeed, 0, this.lastNeed); + return this.lastChar.toString(this.encoding, 0, this.lastTotal); + } + buf.copy(this.lastChar, this.lastTotal - this.lastNeed, 0, buf.length); + this.lastNeed -= buf.length; +}; + +// Checks the type of a UTF-8 byte, whether it's ASCII, a leading byte, or a +// continuation byte. If an invalid byte is detected, -2 is returned. +function utf8CheckByte(byte) { + if (byte <= 0x7F) return 0;else if (byte >> 5 === 0x06) return 2;else if (byte >> 4 === 0x0E) return 3;else if (byte >> 3 === 0x1E) return 4; + return byte >> 6 === 0x02 ? -1 : -2; +} + +// Checks at most 3 bytes at the end of a Buffer in order to detect an +// incomplete multi-byte UTF-8 character. The total number of bytes (2, 3, or 4) +// needed to complete the UTF-8 character (if applicable) are returned. +function utf8CheckIncomplete(self, buf, i) { + var j = buf.length - 1; + if (j < i) return 0; + var nb = utf8CheckByte(buf[j]); + if (nb >= 0) { + if (nb > 0) self.lastNeed = nb - 1; + return nb; + } + if (--j < i || nb === -2) return 0; + nb = utf8CheckByte(buf[j]); + if (nb >= 0) { + if (nb > 0) self.lastNeed = nb - 2; + return nb; + } + if (--j < i || nb === -2) return 0; + nb = utf8CheckByte(buf[j]); + if (nb >= 0) { + if (nb > 0) { + if (nb === 2) nb = 0;else self.lastNeed = nb - 3; + } + return nb; + } + return 0; +} + +// Validates as many continuation bytes for a multi-byte UTF-8 character as +// needed or are available. If we see a non-continuation byte where we expect +// one, we "replace" the validated continuation bytes we've seen so far with +// a single UTF-8 replacement character ('\ufffd'), to match v8's UTF-8 decoding +// behavior. The continuation byte check is included three times in the case +// where all of the continuation bytes for a character exist in the same buffer. +// It is also done this way as a slight performance increase instead of using a +// loop. +function utf8CheckExtraBytes(self, buf, p) { + if ((buf[0] & 0xC0) !== 0x80) { + self.lastNeed = 0; + return '\ufffd'; + } + if (self.lastNeed > 1 && buf.length > 1) { + if ((buf[1] & 0xC0) !== 0x80) { + self.lastNeed = 1; + return '\ufffd'; + } + if (self.lastNeed > 2 && buf.length > 2) { + if ((buf[2] & 0xC0) !== 0x80) { + self.lastNeed = 2; + return '\ufffd'; + } + } + } +} + +// Attempts to complete a multi-byte UTF-8 character using bytes from a Buffer. +function utf8FillLast(buf) { + var p = this.lastTotal - this.lastNeed; + var r = utf8CheckExtraBytes(this, buf, p); + if (r !== undefined) return r; + if (this.lastNeed <= buf.length) { + buf.copy(this.lastChar, p, 0, this.lastNeed); + return this.lastChar.toString(this.encoding, 0, this.lastTotal); + } + buf.copy(this.lastChar, p, 0, buf.length); + this.lastNeed -= buf.length; +} + +// Returns all complete UTF-8 characters in a Buffer. If the Buffer ended on a +// partial character, the character's bytes are buffered until the required +// number of bytes are available. +function utf8Text(buf, i) { + var total = utf8CheckIncomplete(this, buf, i); + if (!this.lastNeed) return buf.toString('utf8', i); + this.lastTotal = total; + var end = buf.length - (total - this.lastNeed); + buf.copy(this.lastChar, 0, end); + return buf.toString('utf8', i, end); +} + +// For UTF-8, a replacement character is added when ending on a partial +// character. +function utf8End(buf) { + var r = buf && buf.length ? this.write(buf) : ''; + if (this.lastNeed) return r + '\ufffd'; + return r; +} + +// UTF-16LE typically needs two bytes per character, but even if we have an even +// number of bytes available, we need to check if we end on a leading/high +// surrogate. In that case, we need to wait for the next two bytes in order to +// decode the last character properly. +function utf16Text(buf, i) { + if ((buf.length - i) % 2 === 0) { + var r = buf.toString('utf16le', i); + if (r) { + var c = r.charCodeAt(r.length - 1); + if (c >= 0xD800 && c <= 0xDBFF) { + this.lastNeed = 2; + this.lastTotal = 4; + this.lastChar[0] = buf[buf.length - 2]; + this.lastChar[1] = buf[buf.length - 1]; + return r.slice(0, -1); + } + } + return r; + } + this.lastNeed = 1; + this.lastTotal = 2; + this.lastChar[0] = buf[buf.length - 1]; + return buf.toString('utf16le', i, buf.length - 1); +} + +// For UTF-16LE we do not explicitly append special replacement characters if we +// end on a partial character, we simply let v8 handle that. +function utf16End(buf) { + var r = buf && buf.length ? this.write(buf) : ''; + if (this.lastNeed) { + var end = this.lastTotal - this.lastNeed; + return r + this.lastChar.toString('utf16le', 0, end); + } + return r; +} + +function base64Text(buf, i) { + var n = (buf.length - i) % 3; + if (n === 0) return buf.toString('base64', i); + this.lastNeed = 3 - n; + this.lastTotal = 3; + if (n === 1) { + this.lastChar[0] = buf[buf.length - 1]; + } else { + this.lastChar[0] = buf[buf.length - 2]; + this.lastChar[1] = buf[buf.length - 1]; + } + return buf.toString('base64', i, buf.length - n); +} + +function base64End(buf) { + var r = buf && buf.length ? this.write(buf) : ''; + if (this.lastNeed) return r + this.lastChar.toString('base64', 0, 3 - this.lastNeed); + return r; +} + +// Pass bytes on through for single-byte encodings (e.g. ascii, latin1, hex) +function simpleWrite(buf) { + return buf.toString(this.encoding); +} + +function simpleEnd(buf) { + return buf && buf.length ? this.write(buf) : ''; +} +},{"safe-buffer":83}],86:[function(require,module,exports){ +(function (setImmediate,clearImmediate){ +var nextTick = require('process/browser.js').nextTick; +var apply = Function.prototype.apply; +var slice = Array.prototype.slice; +var immediateIds = {}; +var nextImmediateId = 0; + +// DOM APIs, for completeness + +exports.setTimeout = function() { + return new Timeout(apply.call(setTimeout, window, arguments), clearTimeout); +}; +exports.setInterval = function() { + return new Timeout(apply.call(setInterval, window, arguments), clearInterval); +}; +exports.clearTimeout = +exports.clearInterval = function(timeout) { timeout.close(); }; + +function Timeout(id, clearFn) { + this._id = id; + this._clearFn = clearFn; +} +Timeout.prototype.unref = Timeout.prototype.ref = function() {}; +Timeout.prototype.close = function() { + this._clearFn.call(window, this._id); +}; + +// Does not start the time, just sets up the members needed. +exports.enroll = function(item, msecs) { + clearTimeout(item._idleTimeoutId); + item._idleTimeout = msecs; +}; + +exports.unenroll = function(item) { + clearTimeout(item._idleTimeoutId); + item._idleTimeout = -1; +}; + +exports._unrefActive = exports.active = function(item) { + clearTimeout(item._idleTimeoutId); + + var msecs = item._idleTimeout; + if (msecs >= 0) { + item._idleTimeoutId = setTimeout(function onTimeout() { + if (item._onTimeout) + item._onTimeout(); + }, msecs); + } +}; + +// That's not how node.js implements it but the exposed api is the same. +exports.setImmediate = typeof setImmediate === "function" ? setImmediate : function(fn) { + var id = nextImmediateId++; + var args = arguments.length < 2 ? false : slice.call(arguments, 1); + + immediateIds[id] = true; + + nextTick(function onNextTick() { + if (immediateIds[id]) { + // fn.call() is faster so we optimize for the common use-case + // @see http://jsperf.com/call-apply-segu + if (args) { + fn.apply(null, args); + } else { + fn.call(null); + } + // Prevent ids from leaking + exports.clearImmediate(id); + } + }); + + return id; +}; + +exports.clearImmediate = typeof clearImmediate === "function" ? clearImmediate : function(id) { + delete immediateIds[id]; +}; +}).call(this,require("timers").setImmediate,require("timers").clearImmediate) +},{"process/browser.js":69,"timers":86}],87:[function(require,module,exports){ +(function (global){ + +/** + * Module exports. + */ + +module.exports = deprecate; + +/** + * Mark that a method should not be used. + * Returns a modified function which warns once by default. + * + * If `localStorage.noDeprecation = true` is set, then it is a no-op. + * + * If `localStorage.throwDeprecation = true` is set, then deprecated functions + * will throw an Error when invoked. + * + * If `localStorage.traceDeprecation = true` is set, then deprecated functions + * will invoke `console.trace()` instead of `console.error()`. + * + * @param {Function} fn - the function to deprecate + * @param {String} msg - the string to print to the console when `fn` is invoked + * @returns {Function} a new "deprecated" version of `fn` + * @api public + */ + +function deprecate (fn, msg) { + if (config('noDeprecation')) { + return fn; + } + + var warned = false; + function deprecated() { + if (!warned) { + if (config('throwDeprecation')) { + throw new Error(msg); + } else if (config('traceDeprecation')) { + console.trace(msg); + } else { + console.warn(msg); + } + warned = true; + } + return fn.apply(this, arguments); + } + + return deprecated; +} + +/** + * Checks `localStorage` for boolean values for the given `name`. + * + * @param {String} name + * @returns {Boolean} + * @api private + */ + +function config (name) { + // accessing global.localStorage can trigger a DOMException in sandboxed iframes + try { + if (!global.localStorage) return false; + } catch (_) { + return false; + } + var val = global.localStorage[name]; + if (null == val) return false; + return String(val).toLowerCase() === 'true'; +} + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],88:[function(require,module,exports){ +module.exports = function isBuffer(arg) { + return arg && typeof arg === 'object' + && typeof arg.copy === 'function' + && typeof arg.fill === 'function' + && typeof arg.readUInt8 === 'function'; +} +},{}],89:[function(require,module,exports){ +(function (process,global){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var formatRegExp = /%[sdj%]/g; +exports.format = function(f) { + if (!isString(f)) { + var objects = []; + for (var i = 0; i < arguments.length; i++) { + objects.push(inspect(arguments[i])); + } + return objects.join(' '); + } + + var i = 1; + var args = arguments; + var len = args.length; + var str = String(f).replace(formatRegExp, function(x) { + if (x === '%%') return '%'; + if (i >= len) return x; + switch (x) { + case '%s': return String(args[i++]); + case '%d': return Number(args[i++]); + case '%j': + try { + return JSON.stringify(args[i++]); + } catch (_) { + return '[Circular]'; + } + default: + return x; + } + }); + for (var x = args[i]; i < len; x = args[++i]) { + if (isNull(x) || !isObject(x)) { + str += ' ' + x; + } else { + str += ' ' + inspect(x); + } + } + return str; +}; + + +// Mark that a method should not be used. +// Returns a modified function which warns once by default. +// If --no-deprecation is set, then it is a no-op. +exports.deprecate = function(fn, msg) { + // Allow for deprecating things in the process of starting up. + if (isUndefined(global.process)) { + return function() { + return exports.deprecate(fn, msg).apply(this, arguments); + }; + } + + if (process.noDeprecation === true) { + return fn; + } + + var warned = false; + function deprecated() { + if (!warned) { + if (process.throwDeprecation) { + throw new Error(msg); + } else if (process.traceDeprecation) { + console.trace(msg); + } else { + console.error(msg); + } + warned = true; + } + return fn.apply(this, arguments); + } + + return deprecated; +}; + + +var debugs = {}; +var debugEnviron; +exports.debuglog = function(set) { + if (isUndefined(debugEnviron)) + debugEnviron = process.env.NODE_DEBUG || ''; + set = set.toUpperCase(); + if (!debugs[set]) { + if (new RegExp('\\b' + set + '\\b', 'i').test(debugEnviron)) { + var pid = process.pid; + debugs[set] = function() { + var msg = exports.format.apply(exports, arguments); + console.error('%s %d: %s', set, pid, msg); + }; + } else { + debugs[set] = function() {}; + } + } + return debugs[set]; +}; + + +/** + * Echos the value of a value. Trys to print the value out + * in the best way possible given the different types. + * + * @param {Object} obj The object to print out. + * @param {Object} opts Optional options object that alters the output. + */ +/* legacy: obj, showHidden, depth, colors*/ +function inspect(obj, opts) { + // default options + var ctx = { + seen: [], + stylize: stylizeNoColor + }; + // legacy... + if (arguments.length >= 3) ctx.depth = arguments[2]; + if (arguments.length >= 4) ctx.colors = arguments[3]; + if (isBoolean(opts)) { + // legacy... + ctx.showHidden = opts; + } else if (opts) { + // got an "options" object + exports._extend(ctx, opts); + } + // set default options + if (isUndefined(ctx.showHidden)) ctx.showHidden = false; + if (isUndefined(ctx.depth)) ctx.depth = 2; + if (isUndefined(ctx.colors)) ctx.colors = false; + if (isUndefined(ctx.customInspect)) ctx.customInspect = true; + if (ctx.colors) ctx.stylize = stylizeWithColor; + return formatValue(ctx, obj, ctx.depth); +} +exports.inspect = inspect; + + +// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics +inspect.colors = { + 'bold' : [1, 22], + 'italic' : [3, 23], + 'underline' : [4, 24], + 'inverse' : [7, 27], + 'white' : [37, 39], + 'grey' : [90, 39], + 'black' : [30, 39], + 'blue' : [34, 39], + 'cyan' : [36, 39], + 'green' : [32, 39], + 'magenta' : [35, 39], + 'red' : [31, 39], + 'yellow' : [33, 39] +}; + +// Don't use 'blue' not visible on cmd.exe +inspect.styles = { + 'special': 'cyan', + 'number': 'yellow', + 'boolean': 'yellow', + 'undefined': 'grey', + 'null': 'bold', + 'string': 'green', + 'date': 'magenta', + // "name": intentionally not styling + 'regexp': 'red' +}; + + +function stylizeWithColor(str, styleType) { + var style = inspect.styles[styleType]; + + if (style) { + return '\u001b[' + inspect.colors[style][0] + 'm' + str + + '\u001b[' + inspect.colors[style][1] + 'm'; + } else { + return str; + } +} + + +function stylizeNoColor(str, styleType) { + return str; +} + + +function arrayToHash(array) { + var hash = {}; + + array.forEach(function(val, idx) { + hash[val] = true; + }); + + return hash; +} + + +function formatValue(ctx, value, recurseTimes) { + // Provide a hook for user-specified inspect functions. + // Check that value is an object with an inspect function on it + if (ctx.customInspect && + value && + isFunction(value.inspect) && + // Filter out the util module, it's inspect function is special + value.inspect !== exports.inspect && + // Also filter out any prototype objects using the circular check. + !(value.constructor && value.constructor.prototype === value)) { + var ret = value.inspect(recurseTimes, ctx); + if (!isString(ret)) { + ret = formatValue(ctx, ret, recurseTimes); + } + return ret; + } + + // Primitive types cannot have properties + var primitive = formatPrimitive(ctx, value); + if (primitive) { + return primitive; + } + + // Look up the keys of the object. + var keys = Object.keys(value); + var visibleKeys = arrayToHash(keys); + + if (ctx.showHidden) { + keys = Object.getOwnPropertyNames(value); + } + + // IE doesn't make error fields non-enumerable + // http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx + if (isError(value) + && (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) { + return formatError(value); + } + + // Some type of object without properties can be shortcutted. + if (keys.length === 0) { + if (isFunction(value)) { + var name = value.name ? ': ' + value.name : ''; + return ctx.stylize('[Function' + name + ']', 'special'); + } + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } + if (isDate(value)) { + return ctx.stylize(Date.prototype.toString.call(value), 'date'); + } + if (isError(value)) { + return formatError(value); + } + } + + var base = '', array = false, braces = ['{', '}']; + + // Make Array say that they are Array + if (isArray(value)) { + array = true; + braces = ['[', ']']; + } + + // Make functions say that they are functions + if (isFunction(value)) { + var n = value.name ? ': ' + value.name : ''; + base = ' [Function' + n + ']'; + } + + // Make RegExps say that they are RegExps + if (isRegExp(value)) { + base = ' ' + RegExp.prototype.toString.call(value); + } + + // Make dates with properties first say the date + if (isDate(value)) { + base = ' ' + Date.prototype.toUTCString.call(value); + } + + // Make error with message first say the error + if (isError(value)) { + base = ' ' + formatError(value); + } + + if (keys.length === 0 && (!array || value.length == 0)) { + return braces[0] + base + braces[1]; + } + + if (recurseTimes < 0) { + if (isRegExp(value)) { + return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); + } else { + return ctx.stylize('[Object]', 'special'); + } + } + + ctx.seen.push(value); + + var output; + if (array) { + output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); + } else { + output = keys.map(function(key) { + return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); + }); + } + + ctx.seen.pop(); + + return reduceToSingleString(output, base, braces); +} + + +function formatPrimitive(ctx, value) { + if (isUndefined(value)) + return ctx.stylize('undefined', 'undefined'); + if (isString(value)) { + var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') + .replace(/'/g, "\\'") + .replace(/\\"/g, '"') + '\''; + return ctx.stylize(simple, 'string'); + } + if (isNumber(value)) + return ctx.stylize('' + value, 'number'); + if (isBoolean(value)) + return ctx.stylize('' + value, 'boolean'); + // For some reason typeof null is "object", so special case here. + if (isNull(value)) + return ctx.stylize('null', 'null'); +} + + +function formatError(value) { + return '[' + Error.prototype.toString.call(value) + ']'; +} + + +function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { + var output = []; + for (var i = 0, l = value.length; i < l; ++i) { + if (hasOwnProperty(value, String(i))) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + String(i), true)); + } else { + output.push(''); + } + } + keys.forEach(function(key) { + if (!key.match(/^\d+$/)) { + output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, + key, true)); + } + }); + return output; +} + + +function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { + var name, str, desc; + desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] }; + if (desc.get) { + if (desc.set) { + str = ctx.stylize('[Getter/Setter]', 'special'); + } else { + str = ctx.stylize('[Getter]', 'special'); + } + } else { + if (desc.set) { + str = ctx.stylize('[Setter]', 'special'); + } + } + if (!hasOwnProperty(visibleKeys, key)) { + name = '[' + key + ']'; + } + if (!str) { + if (ctx.seen.indexOf(desc.value) < 0) { + if (isNull(recurseTimes)) { + str = formatValue(ctx, desc.value, null); + } else { + str = formatValue(ctx, desc.value, recurseTimes - 1); + } + if (str.indexOf('\n') > -1) { + if (array) { + str = str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n').substr(2); + } else { + str = '\n' + str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n'); + } + } + } else { + str = ctx.stylize('[Circular]', 'special'); + } + } + if (isUndefined(name)) { + if (array && key.match(/^\d+$/)) { + return str; + } + name = JSON.stringify('' + key); + if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { + name = name.substr(1, name.length - 2); + name = ctx.stylize(name, 'name'); + } else { + name = name.replace(/'/g, "\\'") + .replace(/\\"/g, '"') + .replace(/(^"|"$)/g, "'"); + name = ctx.stylize(name, 'string'); + } + } + + return name + ': ' + str; +} + + +function reduceToSingleString(output, base, braces) { + var numLinesEst = 0; + var length = output.reduce(function(prev, cur) { + numLinesEst++; + if (cur.indexOf('\n') >= 0) numLinesEst++; + return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1; + }, 0); + + if (length > 60) { + return braces[0] + + (base === '' ? '' : base + '\n ') + + ' ' + + output.join(',\n ') + + ' ' + + braces[1]; + } + + return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; +} + + +// NOTE: These type checking functions intentionally don't use `instanceof` +// because it is fragile and can be easily faked with `Object.create()`. +function isArray(ar) { + return Array.isArray(ar); +} +exports.isArray = isArray; + +function isBoolean(arg) { + return typeof arg === 'boolean'; +} +exports.isBoolean = isBoolean; + +function isNull(arg) { + return arg === null; +} +exports.isNull = isNull; + +function isNullOrUndefined(arg) { + return arg == null; +} +exports.isNullOrUndefined = isNullOrUndefined; + +function isNumber(arg) { + return typeof arg === 'number'; +} +exports.isNumber = isNumber; + +function isString(arg) { + return typeof arg === 'string'; +} +exports.isString = isString; + +function isSymbol(arg) { + return typeof arg === 'symbol'; +} +exports.isSymbol = isSymbol; + +function isUndefined(arg) { + return arg === void 0; +} +exports.isUndefined = isUndefined; + +function isRegExp(re) { + return isObject(re) && objectToString(re) === '[object RegExp]'; +} +exports.isRegExp = isRegExp; + +function isObject(arg) { + return typeof arg === 'object' && arg !== null; +} +exports.isObject = isObject; + +function isDate(d) { + return isObject(d) && objectToString(d) === '[object Date]'; +} +exports.isDate = isDate; + +function isError(e) { + return isObject(e) && + (objectToString(e) === '[object Error]' || e instanceof Error); +} +exports.isError = isError; + +function isFunction(arg) { + return typeof arg === 'function'; +} +exports.isFunction = isFunction; + +function isPrimitive(arg) { + return arg === null || + typeof arg === 'boolean' || + typeof arg === 'number' || + typeof arg === 'string' || + typeof arg === 'symbol' || // ES6 symbol + typeof arg === 'undefined'; +} +exports.isPrimitive = isPrimitive; + +exports.isBuffer = require('./support/isBuffer'); + +function objectToString(o) { + return Object.prototype.toString.call(o); +} + + +function pad(n) { + return n < 10 ? '0' + n.toString(10) : n.toString(10); +} + + +var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', + 'Oct', 'Nov', 'Dec']; + +// 26 Feb 16:19:34 +function timestamp() { + var d = new Date(); + var time = [pad(d.getHours()), + pad(d.getMinutes()), + pad(d.getSeconds())].join(':'); + return [d.getDate(), months[d.getMonth()], time].join(' '); +} + + +// log is just a thin wrapper to console.log that prepends a timestamp +exports.log = function() { + console.log('%s - %s', timestamp(), exports.format.apply(exports, arguments)); +}; + + +/** + * Inherit the prototype methods from one constructor into another. + * + * The Function.prototype.inherits from lang.js rewritten as a standalone + * function (not on Function.prototype). NOTE: If this file is to be loaded + * during bootstrapping this function needs to be rewritten using some native + * functions as prototype setup using normal JavaScript does not work as + * expected during bootstrapping (see mirror.js in r114903). + * + * @param {function} ctor Constructor function which needs to inherit the + * prototype. + * @param {function} superCtor Constructor function to inherit prototype from. + */ +exports.inherits = require('inherits'); + +exports._extend = function(origin, add) { + // Don't do anything if add isn't an object + if (!add || !isObject(add)) return origin; + + var keys = Object.keys(add); + var i = keys.length; + while (i--) { + origin[keys[i]] = add[keys[i]]; + } + return origin; +}; + +function hasOwnProperty(obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); +} + +}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./support/isBuffer":88,"_process":69,"inherits":56}],90:[function(require,module,exports){ +module.exports={ + "name": "mocha", + "version": "7.1.2", + "homepage": "https://mochajs.org/", + "notifyLogo": "https://ibin.co/4QuRuGjXvl36.png" +} +},{}]},{},[1]); diff --git a/tests/frontend/lib/sendkeys.js b/tests/frontend/lib/sendkeys.js index b57618476..7550df38f 100644 --- a/tests/frontend/lib/sendkeys.js +++ b/tests/frontend/lib/sendkeys.js @@ -305,11 +305,11 @@ var START_TO_END = 1; var END_TO_END = 2; var END_TO_START = 3; // from the Mozilla documentation, for range.compareBoundaryPoints(how, sourceRange) -// -1, 0, or 1, indicating whether the corresponding boundary-point of range is respectively before, equal to, or after the corresponding boundary-point of sourceRange. +// -1, 0, or 1, indicating whether the corresponding boundary-point of range is respectively before, equal to, or after the corresponding boundary-point of sourceRange. // * Range.END_TO_END compares the end boundary-point of sourceRange to the end boundary-point of range. // * Range.END_TO_START compares the end boundary-point of sourceRange to the start boundary-point of range. // * Range.START_TO_END compares the start boundary-point of sourceRange to the end boundary-point of range. - // * Range.START_TO_START compares the start boundary-point of sourceRange to the start boundary-point of range. + // * Range.START_TO_START compares the start boundary-point of sourceRange to the start boundary-point of range. function w3cstart(rng, constraint){ if (rng.compareBoundaryPoints (START_TO_START, constraint) <= 0) return 0; // at or before the beginning if (rng.compareBoundaryPoints (END_TO_START, constraint) >= 0) return constraint.toString().length; diff --git a/tests/frontend/runner.css b/tests/frontend/runner.css index 7d5bb7838..14cc96986 100644 --- a/tests/frontend/runner.css +++ b/tests/frontend/runner.css @@ -1,11 +1,14 @@ html { height: 100%; -} +} body { padding: 0px; margin: 0px; height: 100%; + display: flex; + flex-direction: row; + overflow: hidden; } #console { @@ -13,34 +16,34 @@ body { } #iframe-container { - width: 50%; + width: 80%; + min-width: 820px; height: 100%; } #iframe-container iframe { height: 100%; - position:absolute; - min-width:500px; - max-width:800px; - left:50%; width:100%; } #mocha { font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; border-right: 2px solid #999; - width: 50%; + flex: 1 auto; height: 100%; - position: absolute; overflow: auto; - float:left; + width:20%; + font-size:80%; + } #mocha #report { - margin-top: 50px; + margin: 0; + padding: 0; + margin-top: 10px; } -#mocha ul, #mocha li { +#mocha li { margin: 0; padding: 0; } @@ -60,7 +63,7 @@ body { } #mocha h1 a:visited -{ +{ color: #00E; } @@ -76,11 +79,11 @@ body { } #mocha .suite { - margin-left: 15px; + margin-left: 0px; } #mocha .test { - margin-left: 15px; + margin-left: 5px; } #mocha .test:hover h2::after { @@ -175,6 +178,10 @@ body { -webkit-box-shadow: 0 1px 3px #eee; } +#report ul { + padding: 0; +} + #report.pass .test.fail { display: none; } @@ -191,17 +198,21 @@ body { } #stats { - position: fixed; - top: 15px; - right: 52%; + padding: 10px; font-size: 12px; margin: 0; color: #888; + text-align: right; } -#stats .progress { +#mocha-stats { + height: 80px; +} + +#mocha-stats .progress { float: right; padding-top: 0; + margin-right:5px; } #stats em { @@ -229,3 +240,7 @@ code .init { color: #2F6FAD } code .string { color: #5890AD } code .keyword { color: #8A6343 } code .number { color: #2F6FAD } + +ul{ + padding-left:5px; +} diff --git a/tests/frontend/runner.js b/tests/frontend/runner.js index e77f6707f..0ab380fb7 100644 --- a/tests/frontend/runner.js +++ b/tests/frontend/runner.js @@ -1,28 +1,68 @@ $(function(){ - function Base(runner) { - var self = this - , stats = this.stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 } - , failures = this.failures = []; + + function stringifyException(exception){ + var err = exception.stack || exception.toString(); + + // FF / Opera do not add the message + if (!~err.indexOf(exception.message)) { + err = exception.message + '\n' + err; + } + + // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we + // check for the result of the stringifying. + if ('[object Error]' == err) err = exception.message; + + // Safari doesn't give you a stack. Let's at least provide a source line. + if (!exception.stack && exception.sourceURL && exception.line !== undefined) { + err += "\n(" + exception.sourceURL + ":" + exception.line + ")"; + } + + return err; + } + + function CustomRunner(runner) { + var stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 }; if (!runner) return; - this.runner = runner; runner.on('start', function(){ stats.start = new Date; }); runner.on('suite', function(suite){ - stats.suites = stats.suites || 0; suite.root || stats.suites++; + if (suite.root) return; + append(suite.title); + level++; }); - runner.on('test end', function(test){ - stats.tests = stats.tests || 0; + runner.on('suite end', function(suite){ + if (suite.root) return; + level--; + + if(level == 0) { + append(""); + } + }); + + // Scroll down test display after each test + let mochaEl = $('#mocha')[0]; + runner.on('test', function(){ + mochaEl.scrollTop = mochaEl.scrollHeight; + }); + + // max time a test is allowed to run + // TODO this should be lowered once timeslider_revision.js is faster + var killTimeout; + runner.on('test end', function(){ stats.tests++; }); runner.on('pass', function(test){ - stats.passes = stats.passes || 0; + if(killTimeout) clearTimeout(killTimeout); + killTimeout = setTimeout(function(){ + append("FINISHED - [red]no test started since 3 minutes, tests stopped[clear]"); + }, 60000 * 3); var medium = test.slow() / 2; test.speed = test.duration > test.slow() @@ -32,43 +72,31 @@ $(function(){ : 'fast'; stats.passes++; + append("->","[green]PASSED[clear] :", test.title," ",test.duration,"ms"); }); runner.on('fail', function(test, err){ - stats.failures = stats.failures || 0; + if(killTimeout) clearTimeout(killTimeout); + killTimeout = setTimeout(function(){ + append("FINISHED - [red]no test started since 3 minutes, tests stopped[clear]"); + }, 60000 * 3); + stats.failures++; test.err = err; - failures.push(test); + append("->","[red]FAILED[clear] :", test.title, stringifyException(test.err)); }); - runner.on('end', function(){ - stats.end = new Date; - stats.duration = new Date - stats.start; - }); + runner.on('pending', function(test){ + if(killTimeout) clearTimeout(killTimeout); + killTimeout = setTimeout(function(){ + append("FINISHED - [red]no test started since 3 minutes, tests stopped[clear]"); + }, 60000 * 3); - runner.on('pending', function(){ stats.pending++; + append("->","[yellow]PENDING[clear]:", test.title); }); - } - /* - This reporter wraps the original html reporter plus reports plain text into a hidden div. - This allows the webdriver client to pick up the test results - */ - var WebdriverAndHtmlReporter = function(html_reporter){ - return function(runner){ - Base.call(this, runner); - - // Scroll down test display after each test - mocha = $('#mocha')[0]; - runner.on('test', function(){ - mocha.scrollTop = mocha.scrollHeight; - }); - - //initalize the html reporter first - html_reporter(runner); - - var $console = $("#console"); + var $console = $("#console"); var level = 0; var append = function(){ var text = Array.prototype.join.apply(arguments, [" "]); @@ -97,68 +125,23 @@ $(function(){ $console.text(oldText + newText + "\\n"); } - runner.on('suite', function(suite){ - if (suite.root) return; - - append(suite.title); - level++; - }); - - runner.on('suite end', function(suite){ - if (suite.root) return; - level--; - - if(level == 0) { - append(""); - } - }); - - var stringifyException = function(exception){ - var err = exception.stack || exception.toString(); - - // FF / Opera do not add the message - if (!~err.indexOf(exception.message)) { - err = exception.message + '\n' + err; - } - - // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we - // check for the result of the stringifying. - if ('[object Error]' == err) err = exception.message; - - // Safari doesn't give you a stack. Let's at least provide a source line. - if (!exception.stack && exception.sourceURL && exception.line !== undefined) { - err += "\n(" + exception.sourceURL + ":" + exception.line + ")"; - } - - return err; - } - - var killTimeout; - runner.on('test end', function(test){ - if ('passed' == test.state) { - append("->","[green]PASSED[clear] :", test.title); - } else if (test.pending) { - append("->","[yellow]PENDING[clear]:", test.title); - } else { - append("->","[red]FAILED[clear] :", test.title, stringifyException(test.err)); - } - - if(killTimeout) clearTimeout(killTimeout); - killTimeout = setTimeout(function(){ - append("FINISHED - [red]no test started since 3 minutes, tests stopped[clear]"); - }, 60000 * 3); - }); - var total = runner.total; runner.on('end', function(){ - if(stats.tests >= total){ - var minutes = Math.floor(stats.duration / 1000 / 60); - var seconds = Math.round((stats.duration / 1000) % 60); - - append("FINISHED -", stats.passes, "tests passed,", stats.failures, "tests failed, duration: " + minutes + ":" + seconds); + stats.end = new Date; + stats.duration = stats.end - stats.start; + var minutes = Math.floor(stats.duration / 1000 / 60); + var seconds = Math.round((stats.duration / 1000) % 60) // chrome < 57 does not like this .toString().padStart("2","0"); + if(stats.tests === total){ + append("FINISHED -", stats.passes, "tests passed,", stats.failures, "tests failed,", stats.pending," pending, duration: " + minutes + ":" + seconds); + } else if (stats.tests > total) { + append("FINISHED - but more tests than planned returned", stats.passes, "tests passed,", stats.failures, "tests failed,", stats.pending," pending, duration: " + minutes + ":" + seconds); + append(total,"tests, but",stats.tests,"returned. There is probably a problem with your async code or error handling, see https://github.com/mochajs/mocha/issues/1327"); + } + else { + append("FINISHED - but not all tests returned", stats.passes, "tests passed,", stats.failures, "tests failed,", stats.pending, "tests pending, duration: " + minutes + ":" + seconds); + append(total,"tests, but only",stats.tests,"returned. Check for failed before/beforeEach-hooks (no `test end` is called for them and subsequent tests of the same suite are skipped), see https://github.com/mochajs/mocha/pull/1043"); } }); - } } //http://stackoverflow.com/questions/1403888/get-url-parameter-with-jquery @@ -170,7 +153,7 @@ $(function(){ //get the list of specs and filter it if requested var specs = specs_list.slice(); - + //inject spec scripts into the dom var $body = $('body'); $.each(specs, function(i, spec){ @@ -189,10 +172,7 @@ $(function(){ mocha.grep(grep); } - mocha.ignoreLeaks(); - - mocha.reporter(WebdriverAndHtmlReporter(mocha._reporter)); - - mocha.run(); + var runner = mocha.run(); + CustomRunner(runner) }); -}); +}); diff --git a/tests/frontend/specs/alphabet.js b/tests/frontend/specs/alphabet.js index ae9570e41..5d16c983a 100644 --- a/tests/frontend/specs/alphabet.js +++ b/tests/frontend/specs/alphabet.js @@ -8,12 +8,12 @@ describe("All the alphabet works n stuff", function(){ }); it("when you enter any char it appears right", function(done) { - var inner$ = helper.padInner$; - var chrome$ = helper.padChrome$; - + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; + //get the first text element out of the inner iframe var firstTextElement = inner$("div").first(); - + // simulate key presses to delete content firstTextElement.sendkeys('{selectall}'); // select all firstTextElement.sendkeys('{del}'); // clear the first line diff --git a/tests/frontend/specs/bold.js b/tests/frontend/specs/bold.js index 888eb6602..94e3a9b5b 100644 --- a/tests/frontend/specs/bold.js +++ b/tests/frontend/specs/bold.js @@ -6,22 +6,22 @@ describe("bold button", function(){ }); it("makes text bold on click", function(done) { - var inner$ = helper.padInner$; - var chrome$ = helper.padChrome$; + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; //get the first text element out of the inner iframe var $firstTextElement = inner$("div").first(); - + //select this text element $firstTextElement.sendkeys('{selectall}'); //get the bold button and click it var $boldButton = chrome$(".buttonicon-bold"); $boldButton.click(); - + //ace creates a new dom element when you press a button, so just get the first text element again var $newFirstTextElement = inner$("div").first(); - + // is there a element now? var isBold = $newFirstTextElement.find("b").length === 1; @@ -44,13 +44,7 @@ describe("bold button", function(){ //select this text element $firstTextElement.sendkeys('{selectall}'); - if(inner$(window)[0].bowser.modernIE){ // if it's IE - var evtType = "keypress"; - }else{ - var evtType = "keydown"; - } - - var e = inner$.Event(evtType); + var e = inner$.Event(helper.evtType); e.ctrlKey = true; // Control key e.which = 66; // b inner$("#innerdocbody").trigger(e); diff --git a/tests/frontend/specs/caret.js b/tests/frontend/specs/caret.js index 14ff8d6a6..b15dd1597 100644 --- a/tests/frontend/specs/caret.js +++ b/tests/frontend/specs/caret.js @@ -198,9 +198,9 @@ console.log(inner$); /* it("Creates N rows, changes height of rows, updates UI by caret key events", function(done){ var inner$ = helper.padInner$; - var chrome$ = helper.padChrome$; + var chrome$ = helper.padChrome$; var numberOfRows = 50; - + //ace creates a new dom element when you press a keystroke, so just get the first text element again var $newFirstTextElement = inner$("div").first(); var originalDivHeight = inner$("div").first().css("height"); @@ -211,7 +211,7 @@ console.log(inner$); }).done(function(){ // Once the DOM has registered the items inner$("div").each(function(index){ // Randomize the item heights (replicates images / headings etc) var random = Math.floor(Math.random() * (50)) + 20; - $(this).css("height", random+"px"); + $(this).css("height", random+"px"); }); console.log(caretPosition(inner$)); @@ -253,7 +253,7 @@ console.log(inner$); keyEvent(inner$, 33, false, false); // doesn't work i++; } - + // Does scrolling back up the pad with the up arrow show the correct contents? helper.waitFor(function(){ // Wait for the new position to be in place try{ @@ -280,7 +280,7 @@ console.log(inner$); helper.waitFor(function(){ // Wait for the new position to be in place return isScrolledIntoView(inner$("div:nth-child(1)"), inner$); // Wait for the DOM to scroll into place }).done(function(){ // Once the DOM has registered the items - expect(true).to.be(true); + expect(true).to.be(true); done(); }); */ @@ -297,20 +297,15 @@ function prepareDocument(n, target){ // generates a random document with random } function keyEvent(target, charCode, ctrl, shift){ // sends a charCode to the window - if(inner$(window)[0].bowser.firefox || inner$(window)[0].bowser.modernIE){ // if it's a mozilla or IE - var evtType = "keypress"; - }else{ - var evtType = "keydown"; - } - var e = target.Event(evtType); - console.log(e); + + var e = target.Event(helper.evtType); if(ctrl){ e.ctrlKey = true; // Control key } if(shift){ e.shiftKey = true; // Shift Key } - e.which = charCode; + e.which = charCode; e.keyCode = charCode; target("#innerdocbody").trigger(e); } @@ -339,6 +334,5 @@ function caretPosition($){ var pos = doc.getSelection(); pos.y = pos.anchorNode.parentElement.offsetTop; pos.x = pos.anchorNode.parentElement.offsetLeft; - console.log(pos); return pos; } diff --git a/tests/frontend/specs/change_user_color.js b/tests/frontend/specs/change_user_color.js new file mode 100644 index 000000000..5969eabe2 --- /dev/null +++ b/tests/frontend/specs/change_user_color.js @@ -0,0 +1,104 @@ +describe("change user color", function(){ + //create a new pad before each test run + beforeEach(function(cb){ + helper.newPad(cb); + this.timeout(60000); + }); + + it("Color picker matches original color and remembers the user color after a refresh", function(done) { + this.timeout(60000); + var chrome$ = helper.padChrome$; + + //click on the settings button to make settings visible + var $userButton = chrome$(".buttonicon-showusers"); + $userButton.click(); + + var $userSwatch = chrome$("#myswatch"); + $userSwatch.click(); + + var fb = chrome$.farbtastic('#colorpicker') + var $colorPickerSave = chrome$("#mycolorpickersave"); + var $colorPickerPreview = chrome$("#mycolorpickerpreview"); + + // Same color represented in two different ways + const testColorHash = '#abcdef' + const testColorRGB = 'rgb(171, 205, 239)' + + // Check that the color picker matches the automatically assigned random color on the swatch. + // NOTE: This has a tiny chance of creating a false positive for passing in the + // off-chance the randomly assigned color is the same as the test color. + expect($colorPickerPreview.css('background-color')).to.be($userSwatch.css('background-color')) + + // The swatch updates as the test color is picked. + fb.setColor(testColorHash) + expect($colorPickerPreview.css('background-color')).to.be(testColorRGB) + $colorPickerSave.click(); + expect($userSwatch.css('background-color')).to.be(testColorRGB) + + setTimeout(function(){ //give it a second to save the color on the server side + helper.newPad({ // get a new pad, but don't clear the cookies + clearCookies: false + , cb: function(){ + var chrome$ = helper.padChrome$; + + //click on the settings button to make settings visible + var $userButton = chrome$(".buttonicon-showusers"); + $userButton.click(); + + var $userSwatch = chrome$("#myswatch"); + $userSwatch.click(); + + var $colorPickerPreview = chrome$("#mycolorpickerpreview"); + + expect($colorPickerPreview.css('background-color')).to.be(testColorRGB) + expect($userSwatch.css('background-color')).to.be(testColorRGB) + + done(); + } + }); + }, 1000); + }); + + it("Own user color is shown when you enter a chat", function(done) { + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; + + var $colorOption = helper.padChrome$('#options-colorscheck'); + if (!$colorOption.is(':checked')) { + $colorOption.click(); + } + + //click on the settings button to make settings visible + var $userButton = chrome$(".buttonicon-showusers"); + $userButton.click(); + + var $userSwatch = chrome$("#myswatch"); + $userSwatch.click(); + + var fb = chrome$.farbtastic('#colorpicker') + var $colorPickerSave = chrome$("#mycolorpickersave"); + + // Same color represented in two different ways + const testColorHash = '#abcdef' + const testColorRGB = 'rgb(171, 205, 239)' + + fb.setColor(testColorHash) + $colorPickerSave.click(); + + //click on the chat button to make chat visible + var $chatButton = chrome$("#chaticon"); + $chatButton.click(); + var $chatInput = chrome$("#chatinput"); + $chatInput.sendkeys('O hi'); // simulate a keypress of typing user + $chatInput.sendkeys('{enter}'); // simulate a keypress of enter actually does evt.which = 10 not 13 + + //check if chat shows up + helper.waitFor(function(){ + return chrome$("#chattext").children("p").length !== 0; // wait until the chat message shows up + }).done(function(){ + var $firstChatMessage = chrome$("#chattext").children("p"); + expect($firstChatMessage.css('background-color')).to.be(testColorRGB); // expect the first chat message to be of the user's color + done(); + }); + }); +}); diff --git a/tests/frontend/specs/change_user_name.js b/tests/frontend/specs/change_user_name.js index ba089c90b..b0a5df15f 100644 --- a/tests/frontend/specs/change_user_name.js +++ b/tests/frontend/specs/change_user_name.js @@ -12,7 +12,7 @@ describe("change username value", function(){ //click on the settings button to make settings visible var $userButton = chrome$(".buttonicon-showusers"); $userButton.click(); - + var $usernameInput = chrome$("#myusernameedit"); $usernameInput.click(); @@ -45,7 +45,7 @@ describe("change username value", function(){ //click on the settings button to make settings visible var $userButton = chrome$(".buttonicon-showusers"); $userButton.click(); - + var $usernameInput = chrome$("#myusernameedit"); $usernameInput.click(); diff --git a/tests/frontend/specs/chat.js b/tests/frontend/specs/chat.js index 7ebb76fb3..4a88379df 100644 --- a/tests/frontend/specs/chat.js +++ b/tests/frontend/specs/chat.js @@ -6,8 +6,8 @@ describe("Chat messages and UI", function(){ }); it("opens chat, sends a message and makes sure it exists on the page", function(done) { - var inner$ = helper.padInner$; - var chrome$ = helper.padChrome$; + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; var chatValue = "JohnMcLear"; //click on the chat button to make chat visible @@ -39,8 +39,8 @@ describe("Chat messages and UI", function(){ }); it("makes sure that an empty message can't be sent", function(done) { - var inner$ = helper.padInner$; - var chrome$ = helper.padChrome$; + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; //click on the chat button to make chat visible var $chatButton = chrome$("#chaticon"); @@ -65,8 +65,8 @@ describe("Chat messages and UI", function(){ }); it("makes chat stick to right side of the screen", function(done) { - var inner$ = helper.padInner$; - var chrome$ = helper.padChrome$; + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; //click on the settings button to make settings visible var $settingsButton = chrome$(".buttonicon-settings"); @@ -75,56 +75,96 @@ describe("Chat messages and UI", function(){ //get the chat selector var $stickychatCheckbox = chrome$("#options-stickychat"); - //select chat always on screen and fire change event - $stickychatCheckbox.attr('selected','selected'); - $stickychatCheckbox.change(); - $stickychatCheckbox.click(); + //select chat always on screen + if (!$stickychatCheckbox.is(':checked')) { + $stickychatCheckbox.click(); + } - //check if chat changed to get the stickychat Class - var $chatbox = chrome$("#chatbox"); - var hasStickyChatClass = $chatbox.hasClass("stickyChat"); - expect(hasStickyChatClass).to.be(true); + // due to animation, we need to make some timeout... + setTimeout(function() { + //check if chat changed to get the stickychat Class + var $chatbox = chrome$("#chatbox"); + var hasStickyChatClass = $chatbox.hasClass("stickyChat"); + expect(hasStickyChatClass).to.be(true); - //select chat always on screen and fire change event - $stickychatCheckbox.attr('selected','selected'); - $stickychatCheckbox.change(); - $stickychatCheckbox.click(); + // select chat always on screen and fire change event + $stickychatCheckbox.click(); + + setTimeout(function() { + //check if chat changed to remove the stickychat Class + var hasStickyChatClass = $chatbox.hasClass("stickyChat"); + expect(hasStickyChatClass).to.be(false); + + done(); + }, 10) + }, 10) - //check if chat changed to remove the stickychat Class - var hasStickyChatClass = $chatbox.hasClass("stickyChat"); - expect(hasStickyChatClass).to.be(false); - done(); }); it("makes chat stick to right side of the screen then makes it one step smaller", function(done) { - var inner$ = helper.padInner$; - var chrome$ = helper.padChrome$; + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; - //click on the settings button to make settings visible - var $settingsButton = chrome$(".buttonicon-settings"); - $settingsButton.click(); + // open chat + chrome$('#chaticon').click(); - //get the chat selector - var $stickychatCheckbox = chrome$("#options-stickychat"); + // select chat always on screen from chatbox + chrome$('.stick-to-screen-btn').click(); - //select chat always on screen and fire change event - $stickychatCheckbox.attr('selected','selected'); - $stickychatCheckbox.change(); - $stickychatCheckbox.click(); + // due to animation, we need to make some timeout... + setTimeout(function() { + //check if chat changed to get the stickychat Class + var $chatbox = chrome$("#chatbox"); + var hasStickyChatClass = $chatbox.hasClass("stickyChat"); + expect(hasStickyChatClass).to.be(true); - //check if chat changed to get the stickychat Class - var $chatbox = chrome$("#chatbox"); - var hasStickyChatClass = $chatbox.hasClass("stickyChat"); - expect(hasStickyChatClass).to.be(true); + // select chat always on screen and fire change event + chrome$('#titlecross').click(); - //select chat always on screen and fire change event - chrome$('#titlecross').click(); + setTimeout(function() { + //check if chat changed to remove the stickychat Class + var hasStickyChatClass = $chatbox.hasClass("stickyChat"); + expect(hasStickyChatClass).to.be(false); - //check if chat changed to remove the stickychat Class - var hasStickyChatClass = $chatbox.hasClass("stickyChat"); - expect(hasStickyChatClass).to.be(false); - - done(); + done(); + }, 10) + }, 10) }); + + xit("Checks showChat=false URL Parameter hides chat then when removed it shows chat", function(done) { + this.timeout(60000); + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; + + setTimeout(function(){ //give it a second to save the username on the server side + helper.newPad({ // get a new pad, but don't clear the cookies + clearCookies: false, + params:{ + showChat: "false" + }, cb: function(){ + var chrome$ = helper.padChrome$; + var chaticon = chrome$("#chaticon"); + // chat should be hidden. + expect(chaticon.is(":visible")).to.be(false); + + setTimeout(function(){ //give it a second to save the username on the server side + helper.newPad({ // get a new pad, but don't clear the cookies + clearCookies: false + , cb: function(){ + var chrome$ = helper.padChrome$; + var chaticon = chrome$("#chaticon"); + // chat should be visible. + expect(chaticon.is(":visible")).to.be(true); + done(); + } + }); + }, 1000); + + } + }); + }, 1000); + + }); + }); diff --git a/tests/frontend/specs/chat_load_messages.js b/tests/frontend/specs/chat_load_messages.js index 7b09b47e1..29040798d 100644 --- a/tests/frontend/specs/chat_load_messages.js +++ b/tests/frontend/specs/chat_load_messages.js @@ -1,21 +1,21 @@ describe("chat-load-messages", function(){ var padName; - + it("creates a pad", function(done) { padName = helper.newPad(done); this.timeout(60000); }); it("adds a lot of messages", function(done) { - var inner$ = helper.padInner$; - var chrome$ = helper.padChrome$; + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; var chatButton = chrome$("#chaticon"); chatButton.click(); var chatInput = chrome$("#chatinput"); var chatText = chrome$("#chattext"); - + this.timeout(60000); - + var messages = 140; for(var i=1; i <= messages; i++) { var num = ''+i; @@ -33,7 +33,7 @@ describe("chat-load-messages", function(){ helper.newPad(done, padName); }); }); - + it("checks initial message count", function(done) { var chatText; var expectedCount = 101; @@ -48,7 +48,7 @@ describe("chat-load-messages", function(){ done(); }); }); - + it("loads more messages", function(done) { var expectedCount = 122; var chrome$ = helper.padChrome$; @@ -56,7 +56,7 @@ describe("chat-load-messages", function(){ chatButton.click(); var chatText = chrome$("#chattext"); var loadMsgBtn = chrome$("#chatloadmessagesbutton"); - + loadMsgBtn.click(); helper.waitFor(function(){ return chatText.children("p").length == expectedCount; @@ -65,7 +65,7 @@ describe("chat-load-messages", function(){ done(); }); }); - + it("checks for button vanishing", function(done) { var expectedDisplay = 'none'; var chrome$ = helper.padChrome$; diff --git a/tests/frontend/specs/clear_authorship_colors.js b/tests/frontend/specs/clear_authorship_colors.js index 1417f63c6..143a8612d 100644 --- a/tests/frontend/specs/clear_authorship_colors.js +++ b/tests/frontend/specs/clear_authorship_colors.js @@ -6,8 +6,8 @@ describe("clear authorship colors button", function(){ }); it("makes text clear authorship colors", function(done) { - var inner$ = helper.padInner$; - var chrome$ = helper.padChrome$; + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; // override the confirm dialogue functioon helper.padChrome$.window.confirm = function(){ @@ -19,7 +19,7 @@ describe("clear authorship colors button", function(){ // Get the original text var originalText = inner$("div").first().text(); - + // Set some new text var sentText = "Hello"; @@ -39,7 +39,6 @@ describe("clear authorship colors button", function(){ $clearauthorshipcolorsButton.click(); // does the first divs span include an author class? - console.log(inner$("div span").first().attr("class")); var hasAuthorClass = inner$("div span").first().attr("class").indexOf("author") !== -1; //expect(hasAuthorClass).to.be(false); @@ -47,13 +46,88 @@ describe("clear authorship colors button", function(){ var hasAuthorClass = inner$("div").first().attr("class").indexOf("author") !== -1; expect(hasAuthorClass).to.be(false); - setTimeout(function(){ + helper.waitFor(function(){ var disconnectVisible = chrome$("div.disconnected").attr("class").indexOf("visible") === -1 - expect(disconnectVisible).to.be(true); - },1000); + return (disconnectVisible === true) + }); + + var disconnectVisible = chrome$("div.disconnected").attr("class").indexOf("visible") === -1 + expect(disconnectVisible).to.be(true); + + done(); + }); + + }); + + it("makes text clear authorship colors and checks it can't be undone", function(done) { + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; + + // override the confirm dialogue functioon + helper.padChrome$.window.confirm = function(){ + return true; + } + + //get the first text element out of the inner iframe + var $firstTextElement = inner$("div").first(); + + // Get the original text + var originalText = inner$("div").first().text(); + + // Set some new text + var sentText = "Hello"; + + //select this text element + $firstTextElement.sendkeys('{selectall}'); + $firstTextElement.sendkeys(sentText); + $firstTextElement.sendkeys('{rightarrow}'); + + helper.waitFor(function(){ + return inner$("div span").first().attr("class").indexOf("author") !== -1; // wait until we have the full value available + }).done(function(){ + //IE hates you if you don't give focus to the inner frame bevore you do a clearAuthorship + inner$("div").first().focus(); + + //get the clear authorship colors button and click it + var $clearauthorshipcolorsButton = chrome$(".buttonicon-clearauthorship"); + $clearauthorshipcolorsButton.click(); + + // does the first divs span include an author class? + var hasAuthorClass = inner$("div span").first().attr("class").indexOf("author") !== -1; + //expect(hasAuthorClass).to.be(false); + + // does the first div include an author class? + var hasAuthorClass = inner$("div").first().attr("class").indexOf("author") !== -1; + expect(hasAuthorClass).to.be(false); + + var e = inner$.Event(helper.evtType); + e.ctrlKey = true; // Control key + e.which = 90; // z + inner$("#innerdocbody").trigger(e); // shouldn't od anything + + // does the first div include an author class? + hasAuthorClass = inner$("div").first().attr("class").indexOf("author") !== -1; + expect(hasAuthorClass).to.be(false); + + // get undo and redo buttons + var $undoButton = chrome$(".buttonicon-undo"); + + // click the button + $undoButton.click(); // shouldn't do anything + hasAuthorClass = inner$("div").first().attr("class").indexOf("author") !== -1; + expect(hasAuthorClass).to.be(false); + + helper.waitFor(function(){ + var disconnectVisible = chrome$("div.disconnected").attr("class").indexOf("visible") === -1 + return (disconnectVisible === true) + }); + + var disconnectVisible = chrome$("div.disconnected").attr("class").indexOf("visible") === -1 + expect(disconnectVisible).to.be(true); done(); }); }); }); + diff --git a/tests/frontend/specs/delete.js b/tests/frontend/specs/delete.js index 86e76f56f..616cd4ddc 100644 --- a/tests/frontend/specs/delete.js +++ b/tests/frontend/specs/delete.js @@ -6,12 +6,12 @@ describe("delete keystroke", function(){ }); it("makes text delete", function(done) { - var inner$ = helper.padInner$; - var chrome$ = helper.padChrome$; - + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; + //get the first text element out of the inner iframe var $firstTextElement = inner$("div").first(); - + // get the original length of this element var elementLength = $firstTextElement.text().length; @@ -25,7 +25,7 @@ describe("delete keystroke", function(){ //ace creates a new dom element when you press a keystroke, so just get the first text element again var $newFirstTextElement = inner$("div").first(); - + // get the new length of this element var newElementLength = $newFirstTextElement.text().length; diff --git a/tests/frontend/specs/embed_value.js b/tests/frontend/specs/embed_value.js index 029f0dd5d..e4cbcaaeb 100644 --- a/tests/frontend/specs/embed_value.js +++ b/tests/frontend/specs/embed_value.js @@ -16,11 +16,11 @@ describe("embed links", function(){ var $embediFrame = $(embedCode); //read and check the frame attributes - var width = $embediFrame.attr("width"); - var height = $embediFrame.attr("height"); - var name = $embediFrame.attr("name"); - expect(width).to.be('600'); - expect(height).to.be('400'); + var width = $embediFrame.attr("width"); + var height = $embediFrame.attr("height"); + var name = $embediFrame.attr("name"); + expect(width).to.be('100%'); + expect(height).to.be('600'); expect(name).to.be(readonly ? "embed_readonly" : "embed_readwrite"); //parse the url @@ -43,7 +43,7 @@ describe("embed links", function(){ } else { expect(url).to.be(helper.padChrome$.window.location.href); } - + //check if all parts of the url are like expected expect(params).to.eql(expectedParams); } @@ -57,7 +57,7 @@ describe("embed links", function(){ describe("the share link", function(){ it("is the actual pad url", function(done){ - var chrome$ = helper.padChrome$; + var chrome$ = helper.padChrome$; //open share dropdown chrome$(".buttonicon-embed").click(); @@ -73,14 +73,14 @@ describe("embed links", function(){ describe("the embed as iframe code", function(){ it("is an iframe with the the correct url parameters and correct size", function(done){ - var chrome$ = helper.padChrome$; + var chrome$ = helper.padChrome$; //open share dropdown chrome$(".buttonicon-embed").click(); //get the link of the share field + the actual pad url and compare them var embedCode = chrome$("#embedinput").val(); - + checkiFrameCode(embedCode, false) done(); @@ -96,7 +96,7 @@ describe("embed links", function(){ describe("the share link", function(){ it("shows a read only url", function(done){ - var chrome$ = helper.padChrome$; + var chrome$ = helper.padChrome$; //open share dropdown chrome$(".buttonicon-embed").click(); @@ -114,7 +114,7 @@ describe("embed links", function(){ describe("the embed as iframe code", function(){ it("is an iframe with the the correct url parameters and correct size", function(done){ - var chrome$ = helper.padChrome$; + var chrome$ = helper.padChrome$; //open share dropdown chrome$(".buttonicon-embed").click(); @@ -125,9 +125,9 @@ describe("embed links", function(){ //get the link of the share field + the actual pad url and compare them var embedCode = chrome$("#embedinput").val(); - + checkiFrameCode(embedCode, true); - + done(); }); }); diff --git a/tests/frontend/specs/enter.js b/tests/frontend/specs/enter.js index baafeded2..9c3457b02 100644 --- a/tests/frontend/specs/enter.js +++ b/tests/frontend/specs/enter.js @@ -6,12 +6,12 @@ describe("enter keystroke", function(){ }); it("creates a new line & puts cursor onto a new line", function(done) { - var inner$ = helper.padInner$; - var chrome$ = helper.padChrome$; - + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; + //get the first text element out of the inner iframe var $firstTextElement = inner$("div").first(); - + // get the original string value minus the last char var originalTextValue = $firstTextElement.text(); @@ -20,7 +20,7 @@ describe("enter keystroke", function(){ //ace creates a new dom element when you press a keystroke, so just get the first text element again var $newFirstTextElement = inner$("div").first(); - + helper.waitFor(function(){ return inner$("div").first().text() === ""; }).done(function(){ diff --git a/tests/frontend/specs/font_type.js b/tests/frontend/specs/font_type.js index d2c7bc636..ad220d882 100644 --- a/tests/frontend/specs/font_type.js +++ b/tests/frontend/specs/font_type.js @@ -5,26 +5,27 @@ describe("font select", function(){ this.timeout(60000); }); - it("makes text monospace", function(done) { - var inner$ = helper.padInner$; - var chrome$ = helper.padChrome$; + it("makes text RobotoMono", function(done) { + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; //click on the settings button to make settings visible var $settingsButton = chrome$(".buttonicon-settings"); $settingsButton.click(); - //get the font menu and monospace option + //get the font menu and RobotoMono option var $viewfontmenu = chrome$("#viewfontmenu"); - var $monospaceoption = $viewfontmenu.find("[value=monospace]"); + var $RobotoMonooption = $viewfontmenu.find("[value=RobotoMono]"); - //select monospace and fire change event - $monospaceoption.attr('selected','selected'); - $viewfontmenu.val("monospace"); + //select RobotoMono and fire change event + // $RobotoMonooption.attr('selected','selected'); + // commenting out above will break safari test + $viewfontmenu.val("RobotoMono"); $viewfontmenu.change(); - //check if font changed to monospace + //check if font changed to RobotoMono var fontFamily = inner$("body").css("font-family").toLowerCase(); - var containsStr = fontFamily.indexOf("monospace"); + var containsStr = fontFamily.indexOf("robotomono"); expect(containsStr).to.not.be(-1); done(); diff --git a/tests/frontend/specs/helper.js b/tests/frontend/specs/helper.js index 8520769aa..1cb712a00 100644 --- a/tests/frontend/specs/helper.js +++ b/tests/frontend/specs/helper.js @@ -20,7 +20,7 @@ describe("the test helper", function(){ }); it("gives me 3 jquery instances of chrome, outer and inner", function(done){ - this.timeout(5000); + this.timeout(10000); helper.newPad(function(){ //check if the jquery selectors have the desired elements @@ -36,17 +36,106 @@ describe("the test helper", function(){ done(); }); }); + // Make sure the cookies are cleared, and make sure that the cookie + // clearing has taken effect at this point in the code. It has been + // observed that the former can happen without the latter if there + // isn't a timeout (within `newPad`) after clearing the cookies. + // However this doesn't seem to always be easily replicated, so this + // timeout may or may end up in the code. None the less, we test here + // to catch it if the bug comes up again. + it("clears cookies", function(done) { + this.timeout(60000); + + // set cookies far into the future to make sure they're not expired yet + window.document.cookie = 'token=foo;expires=Thu, 01 Jan 3030 00:00:00 GMT; path=/'; + window.document.cookie = 'language=bar;expires=Thu, 01 Jan 3030 00:00:00 GMT; path=/'; + + expect(window.document.cookie).to.contain('token=foo'); + expect(window.document.cookie).to.contain('language=bar'); + + helper.newPad(function(){ + // helper function seems to have cleared cookies + // NOTE: this doesn't yet mean it's proven to have taken effect by this point in execution + var firstCookie = window.document.cookie + expect(firstCookie).to.not.contain('token=foo'); + expect(firstCookie).to.not.contain('language=bar'); + + var chrome$ = helper.padChrome$; + + // click on the settings button to make settings visible + var $userButton = chrome$(".buttonicon-showusers"); + $userButton.click(); + + var $usernameInput = chrome$("#myusernameedit"); + $usernameInput.click(); + + $usernameInput.val('John McLear'); + $usernameInput.blur(); + + // Before refreshing, make sure the name is there + expect($usernameInput.val()).to.be('John McLear'); + + // Now that we have a chrome, we can set a pad cookie, so we can confirm it gets wiped as well + chrome$.document.cookie = 'prefsHtml=baz;expires=Thu, 01 Jan 3030 00:00:00 GMT'; + expect(chrome$.document.cookie).to.contain('prefsHtml=baz'); + + // Cookies are weird. Because it's attached to chrome$ (as helper.setPadCookies does), AND we + // didn't put path=/, we shouldn't expect it to be visible on window.document.cookie. Let's just + // be sure. + expect(window.document.cookie).to.not.contain('prefsHtml=baz'); + + setTimeout(function(){ //give it a second to save the username on the server side + helper.newPad(function(){ // get a new pad, let it clear the cookies + var chrome$ = helper.padChrome$; + + // helper function seems to have cleared cookies + // NOTE: this doesn't yet mean cookies were cleared effectively. + // We still need to test below that we're in a new session + expect(window.document.cookie).to.not.contain('token=foo'); + expect(window.document.cookie).to.not.contain('language=bar'); + expect(chrome$.document.cookie).to.contain('prefsHtml=baz'); + expect(window.document.cookie).to.not.contain('prefsHtml=baz'); + + expect(window.document.cookie).to.not.be(firstCookie); + + // click on the settings button to make settings visible + var $userButton = chrome$(".buttonicon-showusers"); + $userButton.click(); + + // confirm that the session was actually cleared + var $usernameInput = chrome$("#myusernameedit"); + expect($usernameInput.val()).to.be(''); + + done(); + }); + }, 1000); + }); + }); + + it("sets pad prefs cookie", function(done) { + this.timeout(60000); + + helper.newPad({ + padPrefs: {foo:"bar"}, + cb: function(){ + var chrome$ = helper.padChrome$; + expect(chrome$.document.cookie).to.contain('prefsHttp=%7B%22'); + expect(chrome$.document.cookie).to.contain('foo%22%3A%22bar'); + done(); + } + }); + }); }); describe("the waitFor method", function(){ it("takes a timeout and waits long enough", function(done){ this.timeout(2000); - var startTime = new Date().getTime(); + var startTime = Date.now(); helper.waitFor(function(){ return false; }, 1500).fail(function(){ - var duration = new Date().getTime() - startTime; + var duration = Date.now() - startTime; expect(duration).to.be.greaterThan(1400); done(); }); @@ -136,7 +225,15 @@ describe("the test helper", function(){ helper.selectLines($startLine, $endLine, startOffset, endOffset); var selection = inner$.document.getSelection(); - expect(cleanText(selection.toString())).to.be("ort lines to t"); + + /* + * replace() is required here because Firefox keeps the line breaks. + * + * I'm not sure this is ideal behavior of getSelection() where the text + * is not consistent between browsers but that's the situation so that's + * how I'm covering it in this test. + */ + expect(cleanText(selection.toString().replace(/(\r\n|\n|\r)/gm,""))).to.be("ort lines to t"); done(); }); @@ -154,7 +251,15 @@ describe("the test helper", function(){ helper.selectLines($startLine, $endLine, startOffset, endOffset); var selection = inner$.document.getSelection(); - expect(cleanText(selection.toString())).to.be("ort lines to test"); + + /* + * replace() is required here because Firefox keeps the line breaks. + * + * I'm not sure this is ideal behavior of getSelection() where the text + * is not consistent between browsers but that's the situation so that's + * how I'm covering it in this test. + */ + expect(cleanText(selection.toString().replace(/(\r\n|\n|\r)/gm,""))).to.be("ort lines to test"); done(); }); @@ -172,7 +277,15 @@ describe("the test helper", function(){ helper.selectLines($startLine, $endLine, startOffset, endOffset); var selection = inner$.document.getSelection(); - expect(cleanText(selection.toString())).to.be("ort lines "); + + /* + * replace() is required here because Firefox keeps the line breaks. + * + * I'm not sure this is ideal behavior of getSelection() where the text + * is not consistent between browsers but that's the situation so that's + * how I'm covering it in this test. + */ + expect(cleanText(selection.toString().replace(/(\r\n|\n|\r)/gm,""))).to.be("ort lines "); done(); }); @@ -190,7 +303,15 @@ describe("the test helper", function(){ helper.selectLines($startLine, $endLine, startOffset, endOffset); var selection = inner$.document.getSelection(); - expect(cleanText(selection.toString())).to.be("ort lines to test"); + + /* + * replace() is required here because Firefox keeps the line breaks. + * + * I'm not sure this is ideal behavior of getSelection() where the text + * is not consistent between browsers but that's the situation so that's + * how I'm covering it in this test. + */ + expect(cleanText(selection.toString().replace(/(\r\n|\n|\r)/gm,""))).to.be("ort lines to test"); done(); }); @@ -205,7 +326,15 @@ describe("the test helper", function(){ helper.selectLines($startLine, $endLine); var selection = inner$.document.getSelection(); - expect(cleanText(selection.toString())).to.be("short lines to test"); + + /* + * replace() is required here because Firefox keeps the line breaks. + * + * I'm not sure this is ideal behavior of getSelection() where the text + * is not consistent between browsers but that's the situation so that's + * how I'm covering it in this test. + */ + expect(cleanText(selection.toString().replace(/(\r\n|\n|\r)/gm,""))).to.be("short lines to test"); done(); }); diff --git a/tests/frontend/specs/importexport.js b/tests/frontend/specs/importexport.js index 2dc002ba0..3466f7cfb 100644 --- a/tests/frontend/specs/importexport.js +++ b/tests/frontend/specs/importexport.js @@ -159,7 +159,7 @@ describe("import functionality", function(){ //
            • bullet4 line 2
            \n\ //
            \n') }) - + var results = exportfunc(helper.padChrome$.window.location.href) expect(results[0][1]).to.be('
            • bullet line 1
            • bullet line 2
              • bullet2 line 1
                  • bullet4 line 2
                  • bullet4 line 2
                  • bullet4 line 2
                • bullet3 line 1
            • bullet2 line 1

            ') expect(results[1][1]).to.be('\t* bullet line 1\n\t* bullet line 2\n\t\t* bullet2 line 1\n\t\t\t\t* bullet4 line 2\n\t\t\t\t* bullet4 line 2\n\t\t\t\t* bullet4 line 2\n\t\t\t* bullet3 line 1\n\t* bullet2 line 1\n\n') @@ -183,11 +183,11 @@ describe("import functionality", function(){
            \n') }) var results = exportfunc(helper.padChrome$.window.location.href) - expect(results[0][1]).to.be('
            • bullet line 1

            • bullet line 2
              • bullet2 line 1

                  • bullet4 line 2 bisu
                  • bullet4 line 2 bs
                  • bullet4 line 2 uuis
                          • foo
                          • foobar bs
                    • foobar

            ') + expect(results[0][1]).to.be('
            • bullet line 1

            • bullet line 2
              • bullet2 line 1

                  • bullet4 line 2 bisu
                  • bullet4 line 2 bs
                  • bullet4 line 2 uuis
                          • foo
                          • foobar bs
                    • foobar

            ') expect(results[1][1]).to.be('\t* bullet line 1\n\n\t* bullet line 2\n\t\t* bullet2 line 1\n\n\t\t\t\t* bullet4 line 2 bisu\n\t\t\t\t* bullet4 line 2 bs\n\t\t\t\t* bullet4 line 2 uuis\n\t\t\t\t\t\t\t\t* foo\n\t\t\t\t\t\t\t\t* foobar bs\n\t\t\t\t\t* foobar\n\n') done() }) - + xit("import a pad with ordered lists from html", function(done){ var importurl = helper.padChrome$.window.location.href+'/import' var htmlWithBullets = '
            1. number 1 line 1
            1. number 2 line 2
            ' diff --git a/tests/frontend/specs/importindents.js b/tests/frontend/specs/importindents.js index 326d9e971..284a90f32 100644 --- a/tests/frontend/specs/importindents.js +++ b/tests/frontend/specs/importindents.js @@ -66,7 +66,7 @@ describe("import indents functionality", function(){ expect(results[1][1]).to.be('\tindent line 1\n\tindent line 2\n\t\tindent2 line 1\n\t\tindent2 line 2\n\n') done() }) - + xit("import a pad with indented lists and newlines from html", function(done){ var importurl = helper.padChrome$.window.location.href+'/import' var htmlWithIndents = '
            • indent line 1

            • indent 1 line 2
              • indent 2 times line 1

              • indent 2 times line 2
            ' @@ -104,7 +104,7 @@ describe("import indents functionality", function(){
            \n') }) var results = exportfunc(helper.padChrome$.window.location.href) - expect(results[0][1]).to.be('
            • indent line 1

            • indent line 2
              • indent2 line 1

                  • indent4 line 2 bisu
                  • indent4 line 2 bs
                  • indent4 line 2 uuis
                          • foo
                          • foobar bs
                    • foobar

            ') + expect(results[0][1]).to.be('
            • indent line 1

            • indent line 2
              • indent2 line 1

                  • indent4 line 2 bisu
                  • indent4 line 2 bs
                  • indent4 line 2 uuis
                          • foo
                          • foobar bs
                    • foobar

            ') expect(results[1][1]).to.be('\tindent line 1\n\n\tindent line 2\n\t\tindent2 line 1\n\n\t\t\t\tindent4 line 2 bisu\n\t\t\t\tindent4 line 2 bs\n\t\t\t\tindent4 line 2 uuis\n\t\t\t\t\t\t\t\tfoo\n\t\t\t\t\t\t\t\tfoobar bs\n\t\t\t\t\tfoobar\n\n') done() }) diff --git a/tests/frontend/specs/indentation.js b/tests/frontend/specs/indentation.js index dd12fc317..2173f9e07 100644 --- a/tests/frontend/specs/indentation.js +++ b/tests/frontend/specs/indentation.js @@ -15,13 +15,7 @@ describe("indentation button", function(){ //select this text element $firstTextElement.sendkeys('{selectall}'); - if(inner$(window)[0].bowser.modernIE){ // if it's IE - var evtType = "keypress"; - }else{ - var evtType = "keydown"; - } - - var e = inner$.Event(evtType); + var e = inner$.Event(helper.evtType); e.keyCode = 9; // tab :| inner$("#innerdocbody").trigger(e); @@ -325,12 +319,7 @@ describe("indentation button", function(){ function pressEnter(){ var inner$ = helper.padInner$; - if(inner$(window)[0].bowser.modernIE){ // if it's IE - var evtType = "keypress"; - }else{ - var evtType = "keydown"; - } - var e = inner$.Event(evtType); + var e = inner$.Event(helper.evtType); e.keyCode = 13; // enter :| inner$("#innerdocbody").trigger(e); } diff --git a/tests/frontend/specs/italic.js b/tests/frontend/specs/italic.js index cecbc1808..3c62e00e8 100644 --- a/tests/frontend/specs/italic.js +++ b/tests/frontend/specs/italic.js @@ -6,22 +6,22 @@ describe("italic some text", function(){ }); it("makes text italic using button", function(done) { - var inner$ = helper.padInner$; - var chrome$ = helper.padChrome$; + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; //get the first text element out of the inner iframe var $firstTextElement = inner$("div").first(); - + //select this text element $firstTextElement.sendkeys('{selectall}'); //get the bold button and click it var $boldButton = chrome$(".buttonicon-italic"); $boldButton.click(); - + //ace creates a new dom element when you press a button, so just get the first text element again var $newFirstTextElement = inner$("div").first(); - + // is there a element now? var isItalic = $newFirstTextElement.find("i").length === 1; @@ -44,13 +44,7 @@ describe("italic some text", function(){ //select this text element $firstTextElement.sendkeys('{selectall}'); - if(inner$(window)[0].bowser.modernIE){ // if it's IE - var evtType = "keypress"; - }else{ - var evtType = "keydown"; - } - - var e = inner$.Event(evtType); + var e = inner$.Event(helper.evtType); e.ctrlKey = true; // Control key e.which = 105; // i inner$("#innerdocbody").trigger(e); diff --git a/tests/frontend/specs/multiple_authors_clear_authorship_colors.js b/tests/frontend/specs/multiple_authors_clear_authorship_colors.js new file mode 100755 index 000000000..58c93cf2f --- /dev/null +++ b/tests/frontend/specs/multiple_authors_clear_authorship_colors.js @@ -0,0 +1,51 @@ +describe('author of pad edition', function() { + // author 1 creates a new pad with some content (regular lines and lists) + before(function(done) { + var padId = helper.newPad(function() { + // make sure pad has at least 3 lines + var $firstLine = helper.padInner$('div').first(); + $firstLine.html("Hello World"); + + // wait for lines to be processed by Etherpad + helper.waitFor(function() { + return $firstLine.text() === 'Hello World'; + }).done(function() { + // Reload pad, to make changes as a second user. Need a timeout here to make sure + // all changes were saved before reloading + setTimeout(function() { + // Expire cookie, so author is changed after reloading the pad. + // See https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie#Example_4_Reset_the_previous_cookie + helper.padChrome$.document.cookie = 'token=foo;expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/'; + + helper.newPad(done, padId); + }, 1000); + }); + }); + this.timeout(60000); + }); + + // author 2 makes some changes on the pad + it('Clears Authorship by second user', function(done) { + clearAuthorship(done); + }); + + var clearAuthorship = function(done){ + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; + + // override the confirm dialogue functioon + helper.padChrome$.window.confirm = function(){ + return true; + } + + //get the clear authorship colors button and click it + var $clearauthorshipcolorsButton = chrome$(".buttonicon-clearauthorship"); + $clearauthorshipcolorsButton.click(); + + // does the first divs span include an author class? + var hasAuthorClass = inner$("div span").first().attr("class").indexOf("author") !== -1; + + expect(hasAuthorClass).to.be(false) + done(); + } +}); diff --git a/tests/frontend/specs/ordered_list.js b/tests/frontend/specs/ordered_list.js index e7509e883..4f81c4564 100644 --- a/tests/frontend/specs/ordered_list.js +++ b/tests/frontend/specs/ordered_list.js @@ -100,7 +100,6 @@ describe("assign ordered list", function(){ }).done(function(){ var $newSecondLine = inner$("div").first().next(); var hasOLElement = $newSecondLine.find("ol li").length === 1; - console.log($newSecondLine.find("ol")); expect(hasOLElement).to.be(true); expect($newSecondLine.text()).to.be("line 2"); var hasLineNumber = $newSecondLine.find("ol").attr("start") === 2; @@ -111,12 +110,7 @@ describe("assign ordered list", function(){ var triggerCtrlShiftShortcut = function(shortcutChar) { var inner$ = helper.padInner$; - if(inner$(window)[0].bowser.modernIE) { // if it's IE - var evtType = "keypress"; - }else{ - var evtType = "keydown"; - } - var e = inner$.Event(evtType); + var e = inner$.Event(helper.evtType); e.ctrlKey = true; e.shiftKey = true; e.which = shortcutChar.toString().charCodeAt(0); @@ -131,3 +125,80 @@ describe("assign ordered list", function(){ } }); + +describe("Pressing Tab in an OL increases and decreases indentation", function(){ + //create a new pad before each test run + beforeEach(function(cb){ + helper.newPad(cb); + this.timeout(60000); + }); + + it("indent and de-indent list item with keypress", function(done){ + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; + + //get the first text element out of the inner iframe + var $firstTextElement = inner$("div").first(); + + //select this text element + $firstTextElement.sendkeys('{selectall}'); + + var $insertorderedlistButton = chrome$(".buttonicon-insertorderedlist"); + $insertorderedlistButton.click(); + + var e = inner$.Event(helper.evtType); + e.keyCode = 9; // tab + inner$("#innerdocbody").trigger(e); + + expect(inner$("div").first().find(".list-number2").length === 1).to.be(true); + e.shiftKey = true; // shift + e.keyCode = 9; // tab + inner$("#innerdocbody").trigger(e); + + helper.waitFor(function(){ + return inner$("div").first().find(".list-number1").length === 1; + }).done(done); + + }); + + +}); + + +describe("Pressing indent/outdent button in an OL increases and decreases indentation and bullet / ol formatting", function(){ + //create a new pad before each test run + beforeEach(function(cb){ + helper.newPad(cb); + this.timeout(60000); + }); + + it("indent and de-indent list item with indent button", function(done){ + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; + + //get the first text element out of the inner iframe + var $firstTextElement = inner$("div").first(); + + //select this text element + $firstTextElement.sendkeys('{selectall}'); + + var $insertorderedlistButton = chrome$(".buttonicon-insertorderedlist"); + $insertorderedlistButton.click(); + + var $indentButton = chrome$(".buttonicon-indent"); + $indentButton.click(); // make it indented twice + + expect(inner$("div").first().find(".list-number2").length === 1).to.be(true); + + var $outdentButton = chrome$(".buttonicon-outdent"); + $outdentButton.click(); // make it deindented to 1 + + helper.waitFor(function(){ + return inner$("div").first().find(".list-number1").length === 1; + }).done(done); + + }); + + +}); + diff --git a/tests/frontend/specs/pad_modal.js b/tests/frontend/specs/pad_modal.js index 80752e4b8..877381737 100644 --- a/tests/frontend/specs/pad_modal.js +++ b/tests/frontend/specs/pad_modal.js @@ -1,6 +1,6 @@ describe('Pad modal', function() { context('when modal is a "force reconnect" message', function() { - var MODAL_SELECTOR = '#connectivity .slowcommit'; + var MODAL_SELECTOR = '#connectivity'; beforeEach(function(done) { helper.newPad(function() { @@ -10,7 +10,7 @@ describe('Pad modal', function() { // wait for modal to be displayed var $modal = helper.padChrome$(MODAL_SELECTOR); helper.waitFor(function() { - return $modal.is(':visible'); + return $modal.hasClass('popup-show'); }, 50000).done(done); }); @@ -30,7 +30,7 @@ describe('Pad modal', function() { it('does not close the modal', function(done) { var $modal = helper.padChrome$(MODAL_SELECTOR); - var modalIsVisible = $modal.is(':visible'); + var modalIsVisible = $modal.hasClass('popup-show'); expect(modalIsVisible).to.be(true); @@ -45,7 +45,7 @@ describe('Pad modal', function() { it('does not close the modal', function(done) { var $modal = helper.padChrome$(MODAL_SELECTOR); - var modalIsVisible = $modal.is(':visible'); + var modalIsVisible = $modal.hasClass('popup-show'); expect(modalIsVisible).to.be(true); @@ -65,12 +65,13 @@ describe('Pad modal', function() { this.timeout(60000); }); - + // This test breaks safari testing +/* it('does not disable editor', function(done) { expect(isEditorDisabled()).to.be(false); done(); }); - +*/ context('and user clicks on editor', function() { beforeEach(function() { clickOnPadInner(); @@ -126,6 +127,7 @@ describe('Pad modal', function() { var isModalOpened = function(modalSelector) { var $modal = helper.padChrome$(modalSelector); - return $modal.is(':visible'); + + return $modal.hasClass('popup-show'); } }); diff --git a/tests/frontend/specs/redo.js b/tests/frontend/specs/redo.js index caa32feec..be85d44c3 100644 --- a/tests/frontend/specs/redo.js +++ b/tests/frontend/specs/redo.js @@ -7,7 +7,7 @@ describe("undo button then redo button", function(){ it("redo some typing with button", function(done){ var inner$ = helper.padInner$; var chrome$ = helper.padChrome$; - + // get the first text element inside the editable space var $firstTextElement = inner$("div span").first(); var originalValue = $firstTextElement.text(); // get the original value @@ -25,7 +25,6 @@ describe("undo button then redo button", function(){ $redoButton.click(); // resends foo helper.waitFor(function(){ - console.log(inner$("div span").first().text()); return inner$("div span").first().text() === newString; }).done(function(){ var finalValue = inner$("div").first().text(); @@ -47,24 +46,17 @@ describe("undo button then redo button", function(){ var modifiedValue = $firstTextElement.text(); // get the modified value expect(modifiedValue).not.to.be(originalValue); // expect the value to change - if(inner$(window)[0].bowser.firefox || inner$(window)[0].bowser.modernIE){ // if it's a mozilla or IE - var evtType = "keypress"; - }else{ - var evtType = "keydown"; - } - - var e = inner$.Event(evtType); + var e = inner$.Event(helper.evtType); e.ctrlKey = true; // Control key e.which = 90; // z inner$("#innerdocbody").trigger(e); - var e = inner$.Event(evtType); + var e = inner$.Event(helper.evtType); e.ctrlKey = true; // Control key e.which = 121; // y inner$("#innerdocbody").trigger(e); helper.waitFor(function(){ - console.log(inner$("div span").first().text()); return inner$("div span").first().text() === newString; }).done(function(){ var finalValue = inner$("div").first().text(); diff --git a/tests/frontend/specs/responsiveness.js b/tests/frontend/specs/responsiveness.js index ff7dace17..8cc41080c 100644 --- a/tests/frontend/specs/responsiveness.js +++ b/tests/frontend/specs/responsiveness.js @@ -49,7 +49,7 @@ describe('Responsiveness of Editor', function() { }).done(function(){ expect( inner$('div').text().length ).to.be.greaterThan( length ); // has the text changed? - var start = new Date().getTime(); // get the start time + var start = Date.now(); // get the start time // send some new text to the screen (ensure all 3 key events are sent) var el = inner$('div').first(); @@ -65,11 +65,10 @@ describe('Responsiveness of Editor', function() { helper.waitFor(function(){ // Wait for the ability to process return true; // Ghetto but works for now }).done(function(){ - var end = new Date().getTime(); // get the current time + var end = Date.now(); // get the current time var delay = end - start; // get the delay as the current time minus the start time - console.log('delay:', delay); - expect(delay).to.be.below(200); + expect(delay).to.be.below(300); done(); }, 1000); diff --git a/tests/frontend/specs/scroll.js b/tests/frontend/specs/scroll.js deleted file mode 100644 index 94756b856..000000000 --- a/tests/frontend/specs/scroll.js +++ /dev/null @@ -1,649 +0,0 @@ -describe('scroll when focus line is out of viewport', function () { - before(function (done) { - helper.newPad(function(){ - cleanPad(function(){ - forceUseMonospacedFont(); - scrollWhenPlaceCaretInTheLastLineOfViewport(); - createPadWithSeveralLines(function(){ - resizeEditorHeight(); - done(); - }); - }); - }); - this.timeout(20000); - }); - - context('when user presses any arrow keys on a line above the viewport', function(){ - context('and scroll percentage config is set to 0.2 on settings.json', function(){ - var lineCloseOfTopOfPad = 10; - before(function (done) { - setScrollPercentageWhenFocusLineIsOutOfViewport(0.2, true); - scrollEditorToBottomOfPad(); - - placeCaretInTheBeginningOfLine(lineCloseOfTopOfPad, function(){ // place caret in the 10th line - // warning: even pressing right arrow, the caret does not change of position - // the column where the caret is, it has not importance, only the line - pressAndReleaseRightArrow(); - done(); - }); - }); - - it('keeps the focus line scrolled 20% from the top of the viewport', function (done) { - // default behavior is to put the line in the top of viewport, but as - // scrollPercentageWhenFocusLineIsOutOfViewport is set to 0.2, we have an extra 20% of lines scrolled - // (2 lines, which are the 20% of the 10 that are visible on viewport) - var firstLineOfViewport = getFirstLineVisibileOfViewport(); - expect(lineCloseOfTopOfPad).to.be(firstLineOfViewport + 2); - done(); - }); - }); - }); - - context('when user presses any arrow keys on a line below the viewport', function(){ - context('and scroll percentage config is set to 0.7 on settings.json', function(){ - var lineCloseToBottomOfPad = 50; - before(function (done) { - setScrollPercentageWhenFocusLineIsOutOfViewport(0.7); - - // firstly, scroll to make the lineCloseToBottomOfPad visible. After that, scroll to make it out of viewport - scrollEditorToTopOfPad(); - placeCaretAtTheEndOfLine(lineCloseToBottomOfPad); // place caret in the 50th line - setTimeout(function() { - // warning: even pressing right arrow, the caret does not change of position - pressAndReleaseLeftArrow(); - done(); - }, 1000); - }); - - it('keeps the focus line scrolled 70% from the bottom of the viewport', function (done) { - // default behavior is to put the line in the top of viewport, but as - // scrollPercentageWhenFocusLineIsOutOfViewport is set to 0.7, we have an extra 70% of lines scrolled - // (7 lines, which are the 70% of the 10 that are visible on viewport) - var lastLineOfViewport = getLastLineVisibleOfViewport(); - expect(lineCloseToBottomOfPad).to.be(lastLineOfViewport - 7); - done(); - }); - }); - }); - - context('when user presses arrow up on the first line of the viewport', function(){ - context('and percentageToScrollWhenUserPressesArrowUp is set to 0.3', function () { - var lineOnTopOfViewportWhenThePadIsScrolledDown; - before(function (done) { - setPercentageToScrollWhenUserPressesArrowUp(0.3); - - // we need some room to make the scroll up - scrollEditorToBottomOfPad(); - lineOnTopOfViewportWhenThePadIsScrolledDown = 91; - placeCaretAtTheEndOfLine(lineOnTopOfViewportWhenThePadIsScrolledDown); - setTimeout(function() { - // warning: even pressing up arrow, the caret does not change of position - pressAndReleaseUpArrow(); - done(); - }, 1000); - }); - - it('keeps the focus line scrolled 30% of the top of the viewport', function (done) { - // default behavior is to put the line in the top of viewport, but as - // PercentageToScrollWhenUserPressesArrowUp is set to 0.3, we have an extra 30% of lines scrolled - // (3 lines, which are the 30% of the 10 that are visible on viewport) - var firstLineOfViewport = getFirstLineVisibileOfViewport(); - expect(firstLineOfViewport).to.be(lineOnTopOfViewportWhenThePadIsScrolledDown - 3); - done(); - }) - }); - }); - - context('when user edits the last line of viewport', function(){ - context('and scroll percentage config is set to 0 on settings.json', function(){ - var lastLineOfViewportBeforeEnter = 10; - before(function () { - // the default value - resetScrollPercentageWhenFocusLineIsOutOfViewport(); - - // make sure the last line on viewport is the 10th one - scrollEditorToTopOfPad(); - placeCaretAtTheEndOfLine(lastLineOfViewportBeforeEnter); - pressEnter(); - }); - - it('keeps the focus line on the bottom of the viewport', function (done) { - var lastLineOfViewportAfterEnter = getLastLineVisibleOfViewport(); - expect(lastLineOfViewportAfterEnter).to.be(lastLineOfViewportBeforeEnter + 1); - done(); - }); - }); - - context('and scrollPercentageWhenFocusLineIsOutOfViewport is set to 0.3', function(){ // this value is arbitrary - var lastLineOfViewportBeforeEnter = 9; - before(function () { - setScrollPercentageWhenFocusLineIsOutOfViewport(0.3); - - // make sure the last line on viewport is the 10th one - scrollEditorToTopOfPad(); - placeCaretAtTheEndOfLine(lastLineOfViewportBeforeEnter); - pressBackspace(); - }); - - it('scrolls 30% of viewport up', function (done) { - var lastLineOfViewportAfterEnter = getLastLineVisibleOfViewport(); - // default behavior is to scroll one line at the bottom of viewport, but as - // scrollPercentageWhenFocusLineIsOutOfViewport is set to 0.3, we have an extra 30% of lines scrolled - // (3 lines, which are the 30% of the 10 that are visible on viewport) - expect(lastLineOfViewportAfterEnter).to.be(lastLineOfViewportBeforeEnter + 3); - done(); - }); - }); - - context('and it is set to a value that overflow the interval [0, 1]', function(){ - var lastLineOfViewportBeforeEnter = 10; - before(function(){ - var scrollPercentageWhenFocusLineIsOutOfViewport = 1.5; - scrollEditorToTopOfPad(); - placeCaretAtTheEndOfLine(lastLineOfViewportBeforeEnter); - setScrollPercentageWhenFocusLineIsOutOfViewport(scrollPercentageWhenFocusLineIsOutOfViewport); - pressEnter(); - }); - - it('keeps the default behavior of moving the focus line on the bottom of the viewport', function (done) { - var lastLineOfViewportAfterEnter = getLastLineVisibleOfViewport(); - expect(lastLineOfViewportAfterEnter).to.be(lastLineOfViewportBeforeEnter + 1); - done(); - }); - }); - }); - - context('when user edits a line above the viewport', function(){ - context('and scroll percentage config is set to 0 on settings.json', function(){ - var lineCloseOfTopOfPad = 10; - before(function () { - // the default value - setScrollPercentageWhenFocusLineIsOutOfViewport(0); - - // firstly, scroll to make the lineCloseOfTopOfPad visible. After that, scroll to make it out of viewport - scrollEditorToTopOfPad(); - placeCaretAtTheEndOfLine(lineCloseOfTopOfPad); // place caret in the 10th line - scrollEditorToBottomOfPad(); - pressBackspace(); // edit the line where the caret is, which is above the viewport - }); - - it('keeps the focus line on the top of the viewport', function (done) { - var firstLineOfViewportAfterEnter = getFirstLineVisibileOfViewport(); - expect(firstLineOfViewportAfterEnter).to.be(lineCloseOfTopOfPad); - done(); - }); - }); - - context('and scrollPercentageWhenFocusLineIsOutOfViewport is set to 0.2', function(){ // this value is arbitrary - var lineCloseToBottomOfPad = 50; - before(function () { - // we force the line edited to be above the top of the viewport - setScrollPercentageWhenFocusLineIsOutOfViewport(0.2, true); // set scroll jump to 20% - scrollEditorToTopOfPad(); - placeCaretAtTheEndOfLine(lineCloseToBottomOfPad); - scrollEditorToBottomOfPad(); - pressBackspace(); // edit line - }); - - it('scrolls 20% of viewport down', function (done) { - // default behavior is to scroll one line at the top of viewport, but as - // scrollPercentageWhenFocusLineIsOutOfViewport is set to 0.2, we have an extra 20% of lines scrolled - // (2 lines, which are the 20% of the 10 that are visible on viewport) - var firstLineVisibileOfViewport = getFirstLineVisibileOfViewport(); - expect(lineCloseToBottomOfPad).to.be(firstLineVisibileOfViewport + 2); - done(); - }); - }); - }); - - context('when user places the caret at the last line visible of viewport', function(){ - var lastLineVisible; - context('and scroll percentage config is set to 0 on settings.json', function(){ - before(function (done) { - // reset to the default value - resetScrollPercentageWhenFocusLineIsOutOfViewport(); - - placeCaretInTheBeginningOfLine(0, function(){ // reset caret position - scrollEditorToTopOfPad(); - lastLineVisible = getLastLineVisibleOfViewport(); - placeCaretInTheBeginningOfLine(lastLineVisible, done); // place caret in the 9th line - }); - - }); - - it('does not scroll', function(done){ - setTimeout(function() { - var lastLineOfViewport = getLastLineVisibleOfViewport(); - var lineDoesNotScroll = lastLineOfViewport === lastLineVisible; - expect(lineDoesNotScroll).to.be(true); - done(); - }, 1000); - }); - }); - context('and scroll percentage config is set to 0.5 on settings.json', function(){ - before(function (done) { - setScrollPercentageWhenFocusLineIsOutOfViewport(0.5); - scrollEditorToTopOfPad(); - placeCaretInTheBeginningOfLine(0, function(){ // reset caret position - // this timeout inside a callback is ugly but it necessary to give time to aceSelectionChange - // realizes that the selection has been changed - setTimeout(function() { - lastLineVisible = getLastLineVisibleOfViewport(); - placeCaretInTheBeginningOfLine(lastLineVisible, done); // place caret in the 9th line - }, 1000); - }); - }); - - it('scrolls line to 50% of the viewport', function(done){ - helper.waitFor(function(){ - var lastLineOfViewport = getLastLineVisibleOfViewport(); - var lastLinesScrolledFiveLinesUp = lastLineOfViewport - 5 === lastLineVisible; - return lastLinesScrolledFiveLinesUp; - }).done(done); - }); - }); - }); - - // This is a special case. When user is selecting a text with arrow down or arrow left we have - // to keep the last line selected on focus - context('when the first line selected is out of the viewport and user presses shift arrow down', function(){ - var lastLineOfPad = 99; - before(function (done) { - scrollEditorToTopOfPad(); - - // make a selection bigger than the viewport height - var $firstLineOfSelection = getLine(0); - var $lastLineOfSelection = getLine(lastLineOfPad); - var lengthOfLastLine = $lastLineOfSelection.text().length; - helper.selectLines($firstLineOfSelection, $lastLineOfSelection, 0, lengthOfLastLine); - - // place the last line selected on the viewport - scrollEditorToBottomOfPad(); - - // press a key to make the selection goes down - // although we can't simulate the extending of selection. It's possible to send a key event - // which is captured on ace2_inner scroll function. - pressAndReleaseLeftArrow(true); - done(); - }); - - it('keeps the last line selected on focus', function (done) { - var lastLineOfSelectionIsVisible = isLineOnViewport(lastLineOfPad); - expect(lastLineOfSelectionIsVisible).to.be(true); - done(); - }); - }); - - // In this scenario we avoid the bouncing scroll. E.g Let's suppose we have a big line that is - // the size of the viewport, and its top is above the viewport. When user presses '<-', this line - // will scroll down because the top is out of the viewport. When it scrolls down, the bottom of - // line gets below the viewport so when user presses '<-' again it scrolls up to make the bottom - // of line visible. If user presses arrow keys more than one time, the editor will keep scrolling up and down - context('when the line height is bigger than the scroll amount percentage * viewport height', function(){ - var scrollOfEditorBeforePressKey; - var BIG_LINE_NUMBER = 0; - var MIDDLE_OF_BIG_LINE = 51; - before(function (done) { - createPadWithALineHigherThanViewportHeight(this, BIG_LINE_NUMBER, function(){ - setScrollPercentageWhenFocusLineIsOutOfViewport(0.5); // set any value to force scroll to outside to viewport - var $bigLine = getLine(BIG_LINE_NUMBER); - - // each line has about 5 chars, we place the caret in the middle of the line - helper.selectLines($bigLine, $bigLine, MIDDLE_OF_BIG_LINE, MIDDLE_OF_BIG_LINE); - - scrollEditorToLeaveTopAndBottomOfBigLineOutOfViewport($bigLine); - scrollOfEditorBeforePressKey = getEditorScroll(); - - // press a key to force to scroll - pressAndReleaseRightArrow(); - done(); - }); - }); - - // reset pad to the original text - after(function (done) { - this.timeout(5000); - cleanPad(function(){ - createPadWithSeveralLines(function(){ - resetEditorWidth(); - done(); - }); - }); - }); - - // as the editor.line is inside of the viewport, it should not scroll - it('should not scroll', function (done) { - var scrollOfEditorAfterPressKey = getEditorScroll(); - expect(scrollOfEditorAfterPressKey).to.be(scrollOfEditorBeforePressKey); - done(); - }); - }); - - // Some plugins, for example the ep_page_view, change the editor dimensions. This plugin, for example, - // adds padding-top to the ace_outer, which changes the viewport height - describe('integration with plugins which changes the margin of editor', function(){ - context('when editor dimensions changes', function(){ - before(function () { - // reset the size of editor. Now we show more than 10 lines as in the other tests - resetResizeOfEditorHeight(); - scrollEditorToTopOfPad(); - - // height of the editor viewport - var editorHeight = getEditorHeight(); - - // add a big padding-top, 50% of the viewport - var paddingTopOfAceOuter = editorHeight/2; - var chrome$ = helper.padChrome$; - var $outerIframe = chrome$('iframe'); - $outerIframe.css('padding-top', paddingTopOfAceOuter); - - // we set a big value to check if the scroll is made - setScrollPercentageWhenFocusLineIsOutOfViewport(1); - }); - - context('and user places the caret in the last line visible of the pad', function(){ - var lastLineVisible; - beforeEach(function (done) { - lastLineVisible = getLastLineVisibleOfViewport(); - placeCaretInTheBeginningOfLine(lastLineVisible, done); - }); - - it('scrolls the line where caret is', function(done){ - helper.waitFor(function(){ - var firstLineVisibileOfViewport = getFirstLineVisibileOfViewport(); - var linesScrolled = firstLineVisibileOfViewport !== 0; - return linesScrolled; - }).done(done); - }); - }); - }); - }); - - /* ********************* Helper functions/constants ********************* */ - var TOP_OF_PAGE = 0; - var BOTTOM_OF_PAGE = 5000; // we use a big value to force the page to be scrolled all the way down - var LINES_OF_PAD = 100; - var ENTER = 13; - var BACKSPACE = 8; - var LEFT_ARROW = 37; - var UP_ARROW = 38; - var RIGHT_ARROW = 39; - var LINES_ON_VIEWPORT = 10; - var WIDTH_OF_EDITOR_RESIZED = 100; - var LONG_TEXT_CHARS = 100; - - var cleanPad = function(callback) { - var inner$ = helper.padInner$; - var $padContent = inner$('#innerdocbody'); - $padContent.html(''); - - // wait for Etherpad to re-create first line - helper.waitFor(function(){ - var lineNumber = inner$('div').length; - return lineNumber === 1; - }, 2000).done(callback); - }; - - var createPadWithSeveralLines = function(done) { - var line = 'a
            '; - var $firstLine = helper.padInner$('div').first(); - var lines = line.repeat(LINES_OF_PAD); //arbitrary number, we need to create lines that is over the viewport - $firstLine.html(lines); - - helper.waitFor(function(){ - var linesCreated = helper.padInner$('div').length; - return linesCreated === LINES_OF_PAD; - }, 4000).done(done); - }; - - var createPadWithALineHigherThanViewportHeight = function(test, line, done) { - var viewportHeight = 160; //10 lines * 16px (height of line) - test.timeout(5000); - cleanPad(function(){ - // make the editor smaller to make test easier - // with that width the each line has about 5 chars - resizeEditorWidth(); - - // we create a line with 100 chars, which makes about 20 lines - setLongTextOnLine(line); - helper.waitFor(function () { - var $firstLine = getLine(line); - - var heightOfLine = $firstLine.get(0).getBoundingClientRect().height; - return heightOfLine >= viewportHeight; - }, 4000).done(done); - }); - }; - - var setLongTextOnLine = function(line) { - var $line = getLine(line); - var longText = 'a'.repeat(LONG_TEXT_CHARS); - $line.html(longText); - }; - - // resize the editor to make the tests easier - var resizeEditorHeight = function() { - var chrome$ = helper.padChrome$; - chrome$('#editorcontainer').css('height', getSizeOfViewport()); - }; - - // this makes about 5 chars per line - var resizeEditorWidth = function() { - var chrome$ = helper.padChrome$; - chrome$('#editorcontainer').css('width', WIDTH_OF_EDITOR_RESIZED); - }; - - var resetResizeOfEditorHeight = function() { - var chrome$ = helper.padChrome$; - chrome$('#editorcontainer').css('height', ''); - }; - - var resetEditorWidth = function () { - var chrome$ = helper.padChrome$; - chrome$('#editorcontainer').css('width', ''); - }; - - var getEditorHeight = function() { - var chrome$ = helper.padChrome$; - var $editor = chrome$('#editorcontainer'); - var editorHeight = $editor.get(0).clientHeight; - return editorHeight; - }; - - var getSizeOfViewport = function() { - return getLinePositionOnViewport(LINES_ON_VIEWPORT) - getLinePositionOnViewport(0); - }; - - var scrollPageTo = function(value) { - var outer$ = helper.padOuter$; - var $ace_outer = outer$('#outerdocbody').parent(); - $ace_outer.parent().scrollTop(value); - }; - - var scrollEditorToTopOfPad = function() { - scrollPageTo(TOP_OF_PAGE); - }; - - var scrollEditorToBottomOfPad = function() { - scrollPageTo(BOTTOM_OF_PAGE); - }; - - var scrollEditorToLeaveTopAndBottomOfBigLineOutOfViewport = function ($bigLine) { - var lineHeight = $bigLine.get(0).getBoundingClientRect().height; - var middleOfLine = lineHeight/2; - scrollPageTo(middleOfLine); - }; - - var getLine = function(lineNum) { - var inner$ = helper.padInner$; - var $line = inner$('div').eq(lineNum); - return $line; - }; - - var placeCaretAtTheEndOfLine = function(lineNum) { - var $targetLine = getLine(lineNum); - var lineLength = $targetLine.text().length; - helper.selectLines($targetLine, $targetLine, lineLength, lineLength); - }; - - var placeCaretInTheBeginningOfLine = function(lineNum, cb) { - var $targetLine = getLine(lineNum); - helper.selectLines($targetLine, $targetLine, 0, 0); - helper.waitFor(function() { - var $lineWhereCaretIs = getLineWhereCaretIs(); - return $targetLine.get(0) === $lineWhereCaretIs.get(0); - }).done(cb); - }; - - var getLineWhereCaretIs = function() { - var inner$ = helper.padInner$; - var nodeWhereCaretIs = inner$.document.getSelection().anchorNode; - var $lineWhereCaretIs = $(nodeWhereCaretIs).closest('div'); - return $lineWhereCaretIs; - }; - - var getFirstLineVisibileOfViewport = function() { - return _.find(_.range(0, LINES_OF_PAD - 1), isLineOnViewport); - }; - - var getLastLineVisibleOfViewport = function() { - return _.find(_.range(LINES_OF_PAD - 1, 0, -1), isLineOnViewport); - }; - - var pressKey = function(keyCode, shiftIsPressed){ - var inner$ = helper.padInner$; - var evtType; - if(inner$(window)[0].bowser.modernIE){ // if it's IE - evtType = 'keypress'; - }else{ - evtType = 'keydown'; - } - var e = inner$.Event(evtType); - e.shiftKey = shiftIsPressed; - e.keyCode = keyCode; - e.which = keyCode; // etherpad listens to 'which' - inner$('#innerdocbody').trigger(e); - }; - - var releaseKey = function(keyCode){ - var inner$ = helper.padInner$; - var evtType = 'keyup'; - var e = inner$.Event(evtType); - e.keyCode = keyCode; - e.which = keyCode; // etherpad listens to 'which' - inner$('#innerdocbody').trigger(e); - }; - - var pressEnter = function() { - pressKey(ENTER); - }; - - var pressBackspace = function() { - pressKey(BACKSPACE); - }; - - var pressAndReleaseUpArrow = function() { - pressKey(UP_ARROW); - releaseKey(UP_ARROW); - }; - - var pressAndReleaseRightArrow = function() { - pressKey(RIGHT_ARROW); - releaseKey(RIGHT_ARROW); - }; - - var pressAndReleaseLeftArrow = function(shiftIsPressed) { - pressKey(LEFT_ARROW, shiftIsPressed); - releaseKey(LEFT_ARROW); - }; - - var isLineOnViewport = function(lineNumber) { - // in the function scrollNodeVerticallyIntoView from ace2_inner.js, iframePadTop is used to calculate - // how much scroll is needed. Although the name refers to padding-top, this value is not set on the - // padding-top. - var iframePadTop = 8; - var $line = getLine(lineNumber); - var linePosition = $line.get(0).getBoundingClientRect(); - - // position relative to the current viewport - var linePositionTopOnViewport = linePosition.top - getEditorScroll() + iframePadTop; - var linePositionBottomOnViewport = linePosition.bottom - getEditorScroll(); - - var lineBellowTop = linePositionBottomOnViewport > 0; - var lineAboveBottom = linePositionTopOnViewport < getClientHeightVisible(); - var isVisible = lineBellowTop && lineAboveBottom; - - return isVisible; - }; - - var getEditorScroll = function () { - var outer$ = helper.padOuter$; - var scrollTopFirefox = outer$('#outerdocbody').parent().scrollTop(); // works only on firefox - var scrollTop = outer$('#outerdocbody').scrollTop() || scrollTopFirefox; - return scrollTop; - }; - - // clientHeight includes padding, so we have to subtract it and consider only the visible viewport - var getClientHeightVisible = function () { - var outer$ = helper.padOuter$; - var $ace_outer = outer$('#outerdocbody').parent(); - var ace_outerHeight = $ace_outer.get(0).clientHeight; - var ace_outerPaddingTop = getIntValueOfCSSProperty($ace_outer, 'padding-top'); - var paddingAddedWhenPageViewIsEnable = getPaddingAddedWhenPageViewIsEnable(); - var clientHeight = ace_outerHeight - ( ace_outerPaddingTop + paddingAddedWhenPageViewIsEnable); - - return clientHeight; - }; - - // ep_page_view changes the dimensions of the editor. We have to guarantee - // the viewport height is calculated right - var getPaddingAddedWhenPageViewIsEnable = function () { - var chrome$ = helper.padChrome$; - var $outerIframe = chrome$('iframe'); - var paddingAddedWhenPageViewIsEnable = parseInt($outerIframe.css('padding-top')); - return paddingAddedWhenPageViewIsEnable; - }; - - var getIntValueOfCSSProperty = function($element, property){ - var valueString = $element.css(property); - return parseInt(valueString) || 0; - }; - - var forceUseMonospacedFont = function () { - helper.padChrome$.window.clientVars.padOptions.useMonospaceFont = true; - }; - - var setScrollPercentageWhenFocusLineIsOutOfViewport = function(value, editionAboveViewport) { - var scrollSettings = helper.padChrome$.window.clientVars.scrollWhenFocusLineIsOutOfViewport; - if (editionAboveViewport) { - scrollSettings.percentage.editionAboveViewport = value; - }else{ - scrollSettings.percentage.editionBelowViewport = value; - } - }; - - var resetScrollPercentageWhenFocusLineIsOutOfViewport = function() { - var scrollSettings = helper.padChrome$.window.clientVars.scrollWhenFocusLineIsOutOfViewport; - scrollSettings.percentage.editionAboveViewport = 0; - scrollSettings.percentage.editionBelowViewport = 0; - }; - - var setPercentageToScrollWhenUserPressesArrowUp = function (value) { - var scrollSettings = helper.padChrome$.window.clientVars.scrollWhenFocusLineIsOutOfViewport; - scrollSettings.percentageToScrollWhenUserPressesArrowUp = value; - }; - - var scrollWhenPlaceCaretInTheLastLineOfViewport = function() { - var scrollSettings = helper.padChrome$.window.clientVars.scrollWhenFocusLineIsOutOfViewport; - scrollSettings.scrollWhenCaretIsInTheLastLineOfViewport = true; - }; - - var getLinePositionOnViewport = function(lineNumber) { - var $line = getLine(lineNumber); - var linePosition = $line.get(0).getBoundingClientRect(); - - // position relative to the current viewport - return linePosition.top - getEditorScroll(); - }; -}); - diff --git a/tests/frontend/specs/select_formatting_buttons.js b/tests/frontend/specs/select_formatting_buttons.js index b6ec6d0c3..9bc6f1017 100644 --- a/tests/frontend/specs/select_formatting_buttons.js +++ b/tests/frontend/specs/select_formatting_buttons.js @@ -63,19 +63,23 @@ describe("select formatting buttons when selection has style applied", function( } var applyStyleOnLineOnFullLineAndRemoveSelection = function(line, style, selectTarget, cb) { + // see if line html has changed + var inner$ = helper.padInner$; + var oldLineHTML = inner$.find("div")[line]; applyStyleOnLine(style, line); - // we have to give some time to Etherpad detects the selection changed - setTimeout(function() { + helper.waitFor(function(){ + var lineHTML = inner$.find("div")[line]; + return lineHTML !== oldLineHTML; + }); // remove selection from previous line - selectLine(line + 1); - setTimeout(function() { - // select the text or place the caret on a position that - // has the formatting text applied previously - selectTarget(line); - cb(); - }, 1000); - }, 1000); + selectLine(line + 1); + //setTimeout(function() { + // select the text or place the caret on a position that + // has the formatting text applied previously + selectTarget(line); + cb(); + //}, 1000); } var pressFormattingShortcutOnSelection = function(key) { @@ -88,13 +92,7 @@ describe("select formatting buttons when selection has style applied", function( //select this text element $firstTextElement.sendkeys('{selectall}'); - if(inner$(window)[0].bowser.modernIE){ // if it's IE - var evtType = "keypress"; - }else{ - var evtType = "keydown"; - } - - var e = inner$.Event(evtType); + var e = inner$.Event(helper.evtType); e.ctrlKey = true; // Control key e.which = key.charCodeAt(0); // I, U, B, 5 inner$("#innerdocbody").trigger(e); diff --git a/tests/frontend/specs/strikethrough.js b/tests/frontend/specs/strikethrough.js index 9afcea0fd..dc37b36f4 100644 --- a/tests/frontend/specs/strikethrough.js +++ b/tests/frontend/specs/strikethrough.js @@ -6,22 +6,22 @@ describe("strikethrough button", function(){ }); it("makes text strikethrough", function(done) { - var inner$ = helper.padInner$; - var chrome$ = helper.padChrome$; + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; //get the first text element out of the inner iframe var $firstTextElement = inner$("div").first(); - + //select this text element $firstTextElement.sendkeys('{selectall}'); //get the strikethrough button and click it var $strikethroughButton = chrome$(".buttonicon-strikethrough"); $strikethroughButton.click(); - + //ace creates a new dom element when you press a button, so just get the first text element again var $newFirstTextElement = inner$("div").first(); - + // is there a element now? var isstrikethrough = $newFirstTextElement.find("s").length === 1; diff --git a/tests/frontend/specs/timeslider.js b/tests/frontend/specs/timeslider.js index cb37bacb6..bca80ba49 100644 --- a/tests/frontend/specs/timeslider.js +++ b/tests/frontend/specs/timeslider.js @@ -8,7 +8,7 @@ xdescribe("timeslider button takes you to the timeslider of a pad", function(){ it("timeslider contained in URL", function(done){ var inner$ = helper.padInner$; var chrome$ = helper.padChrome$; - + // get the first text element inside the editable space var $firstTextElement = inner$("div span").first(); var originalValue = $firstTextElement.text(); // get the original value diff --git a/tests/frontend/specs/timeslider_follow.js b/tests/frontend/specs/timeslider_follow.js new file mode 100644 index 000000000..258abc3ec --- /dev/null +++ b/tests/frontend/specs/timeslider_follow.js @@ -0,0 +1,55 @@ +describe("timeslider", function(){ + //create a new pad before each test run + beforeEach(function(cb){ + helper.newPad(cb); + this.timeout(6000); + }); + + it("follow content as it's added to timeslider", function(done) { // passes + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; + + // make some changes to produce 100 revisions + var timePerRev = 900 + , revs = 10; + this.timeout(revs*timePerRev+10000); + for(var i=0; i < revs; i++) { + setTimeout(function() { + // enter 'a' in the first text element + inner$("div").last().sendkeys('a\n'); + inner$("div").last().sendkeys('{enter}'); + inner$("div").last().sendkeys('{enter}'); + inner$("div").last().sendkeys('{enter}'); + inner$("div").last().sendkeys('{enter}'); + }, timePerRev*i); + } + + setTimeout(function() { + // go to timeslider + $('#iframe-container iframe').attr('src', $('#iframe-container iframe').attr('src')+'/timeslider#0'); + + setTimeout(function() { + var timeslider$ = $('#iframe-container iframe')[0].contentWindow.$; + var $sliderBar = timeslider$('#ui-slider-bar'); + + var latestContents = timeslider$('#innerdocbody').text(); + + // set to follow contents as it arrives + timeslider$('#options-followContents').prop("checked", true); + + var originalTop = timeslider$('#innerdocbody').offset(); + timeslider$('#playpause_button_icon').click(); + + setTimeout(function() { + //make sure the text has changed + var newTop = timeslider$('#innerdocbody').offset(); + expect( originalTop ).not.to.eql( newTop ); + done(); + }, 1000); + + }, 2000); + }, revs*timePerRev); + }); + +}); + diff --git a/tests/frontend/specs/timeslider_labels.js b/tests/frontend/specs/timeslider_labels.js index d39685efe..09213a452 100644 --- a/tests/frontend/specs/timeslider_labels.js +++ b/tests/frontend/specs/timeslider_labels.js @@ -6,9 +6,9 @@ describe("timeslider", function(){ }); it("Shows a date and time in the timeslider and make sure it doesn't include NaN", function(done) { - var inner$ = helper.padInner$; - var chrome$ = helper.padChrome$; - + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; + // make some changes to produce 100 revisions var revs = 10; this.timeout(60000); @@ -18,15 +18,15 @@ describe("timeslider", function(){ inner$("div").first().sendkeys('a'); }, 200); } - + setTimeout(function() { // go to timeslider $('#iframe-container iframe').attr('src', $('#iframe-container iframe').attr('src')+'/timeslider'); - + setTimeout(function() { var timeslider$ = $('#iframe-container iframe')[0].contentWindow.$; var $sliderBar = timeslider$('#ui-slider-bar'); - + var latestContents = timeslider$('#padcontent').text(); // Expect the date and time to be shown @@ -36,17 +36,17 @@ describe("timeslider", function(){ e.clientX = e.pageX = 150; e.clientY = e.pageY = 45; $sliderBar.trigger(e); - + e = new jQuery.Event('mousedown'); e.clientX = e.pageX = 150; e.clientY = e.pageY = 40; $sliderBar.trigger(e); - + e = new jQuery.Event('mousedown'); e.clientX = e.pageX = 150; e.clientY = e.pageY = 50; $sliderBar.trigger(e); - + $sliderBar.trigger('mouseup') setTimeout(function() { diff --git a/tests/frontend/specs/timeslider_numeric_padID.js b/tests/frontend/specs/timeslider_numeric_padID.js new file mode 100644 index 000000000..e6071f091 --- /dev/null +++ b/tests/frontend/specs/timeslider_numeric_padID.js @@ -0,0 +1,67 @@ +describe("timeslider", function(){ + var padId = 735773577357+(Math.round(Math.random()*1000)); + + //create a new pad before each test run + beforeEach(function(cb){ + helper.newPad(cb, padId); + this.timeout(60000); + }); + + it("Makes sure the export URIs are as expected when the padID is numeric", function(done) { + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; + + // make some changes to produce 100 revisions + var revs = 10; + this.timeout(60000); + for(var i=0; i < revs; i++) { + setTimeout(function() { + // enter 'a' in the first text element + inner$("div").first().sendkeys('a'); + }, 100); + } + + setTimeout(function() { + // go to timeslider + $('#iframe-container iframe').attr('src', $('#iframe-container iframe').attr('src')+'/timeslider'); + + setTimeout(function() { + var timeslider$ = $('#iframe-container iframe')[0].contentWindow.$; + var $sliderBar = timeslider$('#ui-slider-bar'); + + var latestContents = timeslider$('#padcontent').text(); + + // Expect the date and time to be shown + + // Click somewhere on the timeslider + var e = new jQuery.Event('mousedown'); + e.clientX = e.pageX = 150; + e.clientY = e.pageY = 45; + $sliderBar.trigger(e); + + e = new jQuery.Event('mousedown'); + e.clientX = e.pageX = 150; + e.clientY = e.pageY = 40; + $sliderBar.trigger(e); + + e = new jQuery.Event('mousedown'); + e.clientX = e.pageX = 150; + e.clientY = e.pageY = 50; + $sliderBar.trigger(e); + + $sliderBar.trigger('mouseup') + + setTimeout(function() { + // expect URI to be similar to + // http://192.168.1.48:9001/p/2/2/export/html + // http://192.168.1.48:9001/p/735773577399/0/export/html + var exportLink = timeslider$('#exporthtmla').attr('href'); + var checkVal = padId + "/0/export/html"; + var includesCorrectURI = exportLink.indexOf(checkVal); + expect(includesCorrectURI).to.not.be(-1); + done(); + }, 400); + }, 2000); + }, 2000); + }); +}); diff --git a/tests/frontend/specs/timeslider_revisions.js b/tests/frontend/specs/timeslider_revisions.js index 2afd2e9d3..67123344b 100644 --- a/tests/frontend/specs/timeslider_revisions.js +++ b/tests/frontend/specs/timeslider_revisions.js @@ -6,12 +6,12 @@ describe("timeslider", function(){ }); it("loads adds a hundred revisions", function(done) { // passes - var inner$ = helper.padInner$; - var chrome$ = helper.padChrome$; - + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; + // make some changes to produce 100 revisions var timePerRev = 900 - , revs = 100; + , revs = 99; this.timeout(revs*timePerRev+10000); for(var i=0; i < revs; i++) { setTimeout(function() { @@ -20,43 +20,45 @@ describe("timeslider", function(){ }, timePerRev*i); } chrome$('.buttonicon-savedRevision').click(); - + setTimeout(function() { // go to timeslider $('#iframe-container iframe').attr('src', $('#iframe-container iframe').attr('src')+'/timeslider'); - + setTimeout(function() { var timeslider$ = $('#iframe-container iframe')[0].contentWindow.$; var $sliderBar = timeslider$('#ui-slider-bar'); - - var latestContents = timeslider$('#padcontent').text(); + + var latestContents = timeslider$('#innerdocbody').text(); // Click somewhere on the timeslider var e = new jQuery.Event('mousedown'); + // sets y co-ordinate of the pad slider modal. + var base = (timeslider$('#ui-slider-bar').offset().top - 24) e.clientX = e.pageX = 150; - e.clientY = e.pageY = 45; + e.clientY = e.pageY = base+5; $sliderBar.trigger(e); - + e = new jQuery.Event('mousedown'); e.clientX = e.pageX = 150; - e.clientY = e.pageY = 40; + e.clientY = e.pageY = base; $sliderBar.trigger(e); - + e = new jQuery.Event('mousedown'); e.clientX = e.pageX = 150; - e.clientY = e.pageY = 50; + e.clientY = e.pageY = base-5; $sliderBar.trigger(e); - + $sliderBar.trigger('mouseup') setTimeout(function() { //make sure the text has changed - expect( timeslider$('#padcontent').text() ).not.to.eql( latestContents ); + expect( timeslider$('#innerdocbody').text() ).not.to.eql( latestContents ); var starIsVisible = timeslider$('.star').is(":visible"); expect( starIsVisible ).to.eql( true ); done(); }, 1000); - + }, 6000); }, revs*timePerRev); }); @@ -64,9 +66,9 @@ describe("timeslider", function(){ // Disabled as jquery trigger no longer works properly xit("changes the url when clicking on the timeslider", function(done) { - var inner$ = helper.padInner$; - var chrome$ = helper.padChrome$; - + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; + // make some changes to produce 7 revisions var timePerRev = 1000 , revs = 20; @@ -77,24 +79,24 @@ describe("timeslider", function(){ inner$("div").first().sendkeys('a'); }, timePerRev*i); } - + setTimeout(function() { // go to timeslider $('#iframe-container iframe').attr('src', $('#iframe-container iframe').attr('src')+'/timeslider'); - + setTimeout(function() { var timeslider$ = $('#iframe-container iframe')[0].contentWindow.$; var $sliderBar = timeslider$('#ui-slider-bar'); - - var latestContents = timeslider$('#padcontent').text(); + + var latestContents = timeslider$('#innerdocbody').text(); var oldUrl = $('#iframe-container iframe')[0].contentWindow.location.hash; - + // Click somewhere on the timeslider var e = new jQuery.Event('mousedown'); e.clientX = e.pageX = 150; e.clientY = e.pageY = 60; $sliderBar.trigger(e); - + helper.waitFor(function(){ return $('#iframe-container iframe')[0].contentWindow.location.hash != oldUrl; }, 6000).always(function(){ @@ -105,7 +107,7 @@ describe("timeslider", function(){ }, revs*timePerRev); }); it("jumps to a revision given in the url", function(done) { - var inner$ = helper.padInner$; + var inner$ = helper.padInner$; var chrome$ = helper.padChrome$; this.timeout(20000); @@ -118,7 +120,7 @@ describe("timeslider", function(){ expect( oldLength ).to.not.eql( 0 ); inner$("div").first().sendkeys('a'); var timeslider$; - + // wait for our additional revision to be added helper.waitFor(function(){ // newLines takes the new lines into account which are strippen when using @@ -131,17 +133,17 @@ describe("timeslider", function(){ }, 6000).always(function() { // go to timeslider with a specific revision set $('#iframe-container iframe').attr('src', $('#iframe-container iframe').attr('src')+'/timeslider#0'); - + // wait for the timeslider to be loaded helper.waitFor(function(){ try { timeslider$ = $('#iframe-container iframe')[0].contentWindow.$; } catch(e){} if(timeslider$){ - return timeslider$('#padcontent').text().length == oldLength; + return timeslider$('#innerdocbody').text().length == oldLength; } }, 6000).always(function(){ - expect( timeslider$('#padcontent').text().length ).to.eql( oldLength ); + expect( timeslider$('#innerdocbody').text().length ).to.eql( oldLength ); done(); }); }); @@ -149,17 +151,17 @@ describe("timeslider", function(){ }); it("checks the export url", function(done) { - var inner$ = helper.padInner$; - var chrome$ = helper.padChrome$; + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; this.timeout(11000); inner$("div").first().sendkeys('a'); - + setTimeout(function() { // go to timeslider $('#iframe-container iframe').attr('src', $('#iframe-container iframe').attr('src')+'/timeslider#0'); var timeslider$; var exportLink; - + helper.waitFor(function(){ try{ timeslider$ = $('#iframe-container iframe')[0].contentWindow.$; diff --git a/tests/frontend/specs/undo.js b/tests/frontend/specs/undo.js index 3644734f4..172a2b81e 100644 --- a/tests/frontend/specs/undo.js +++ b/tests/frontend/specs/undo.js @@ -4,11 +4,10 @@ describe("undo button", function(){ this.timeout(60000); }); -/* it("undo some typing by clicking undo button", function(done){ var inner$ = helper.padInner$; var chrome$ = helper.padChrome$; - + // get the first text element inside the editable space var $firstTextElement = inner$("div span").first(); var originalValue = $firstTextElement.text(); // get the original value @@ -30,7 +29,6 @@ describe("undo button", function(){ done(); }); }); -*/ it("undo some typing using a keypress", function(done){ var inner$ = helper.padInner$; @@ -44,13 +42,7 @@ describe("undo button", function(){ var modifiedValue = $firstTextElement.text(); // get the modified value expect(modifiedValue).not.to.be(originalValue); // expect the value to change - /* - * ACHTUNG: this is the only place in the test codebase in which a keydown - * is sent for IE. Everywhere else IE uses keypress. - */ - var evtType = "keydown"; - - var e = inner$.Event(evtType); + var e = inner$.Event(helper.evtType); e.ctrlKey = true; // Control key e.which = 90; // z inner$("#innerdocbody").trigger(e); diff --git a/tests/frontend/specs/unordered_list.js b/tests/frontend/specs/unordered_list.js index 4ea77b8ac..2af843d61 100644 --- a/tests/frontend/specs/unordered_list.js +++ b/tests/frontend/specs/unordered_list.js @@ -6,7 +6,7 @@ describe("assign unordered list", function(){ }); it("insert unordered list text then removes by outdent", function(done){ - var inner$ = helper.padInner$; + var inner$ = helper.padInner$; var chrome$ = helper.padChrome$; var originalText = inner$("div").first().text(); @@ -33,3 +33,145 @@ describe("assign unordered list", function(){ }); }); + +describe("unassign unordered list", function(){ + //create a new pad before each test run + beforeEach(function(cb){ + helper.newPad(cb); + this.timeout(60000); + }); + + it("insert unordered list text then remove by clicking list again", function(done){ + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; + var originalText = inner$("div").first().text(); + + var $insertunorderedlistButton = chrome$(".buttonicon-insertunorderedlist"); + $insertunorderedlistButton.click(); + + helper.waitFor(function(){ + var newText = inner$("div").first().text(); + if(newText === originalText){ + return inner$("div").first().find("ul li").length === 1; + } + }).done(function(){ + + // remove indentation by bullet and ensure text string remains the same + $insertunorderedlistButton.click(); + helper.waitFor(function(){ + var isList = inner$("div").find("ul").length === 1; + // sohuldn't be list + return (isList === false); + }).done(function(){ + done(); + }); + + }); + }); +}); + + +describe("keep unordered list on enter key", function(){ + //create a new pad before each test run + beforeEach(function(cb){ + helper.newPad(cb); + this.timeout(60000); + }); + + it("Keeps the unordered list on enter for the new line", function(done){ + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; + + var $insertorderedlistButton = chrome$(".buttonicon-insertunorderedlist"); + $insertorderedlistButton.click(); + + //type a bit, make a line break and type again + var $firstTextElement = inner$("div span").first(); + $firstTextElement.sendkeys('line 1'); + $firstTextElement.sendkeys('{enter}'); + $firstTextElement.sendkeys('line 2'); + $firstTextElement.sendkeys('{enter}'); + + helper.waitFor(function(){ + return inner$("div span").first().text().indexOf("line 2") === -1; + }).done(function(){ + var $newSecondLine = inner$("div").first().next(); + var hasULElement = $newSecondLine.find("ul li").length === 1; + expect(hasULElement).to.be(true); + expect($newSecondLine.text()).to.be("line 2"); + done(); + }); + }); + +}); + +describe("Pressing Tab in an UL increases and decreases indentation", function(){ + //create a new pad before each test run + beforeEach(function(cb){ + helper.newPad(cb); + this.timeout(60000); + }); + + it("indent and de-indent list item with keypress", function(done){ + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; + + //get the first text element out of the inner iframe + var $firstTextElement = inner$("div").first(); + + //select this text element + $firstTextElement.sendkeys('{selectall}'); + + var $insertorderedlistButton = chrome$(".buttonicon-insertunorderedlist"); + $insertorderedlistButton.click(); + + var e = inner$.Event(helper.evtType); + e.keyCode = 9; // tab + inner$("#innerdocbody").trigger(e); + + expect(inner$("div").first().find(".list-bullet2").length === 1).to.be(true); + e.shiftKey = true; // shift + e.keyCode = 9; // tab + inner$("#innerdocbody").trigger(e); + + helper.waitFor(function(){ + return inner$("div").first().find(".list-bullet1").length === 1; + }).done(done); + + }); + +}); + +describe("Pressing indent/outdent button in an UL increases and decreases indentation and bullet / ol formatting", function(){ + //create a new pad before each test run + beforeEach(function(cb){ + helper.newPad(cb); + this.timeout(60000); + }); + + it("indent and de-indent list item with indent button", function(done){ + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; + + //get the first text element out of the inner iframe + var $firstTextElement = inner$("div").first(); + + //select this text element + $firstTextElement.sendkeys('{selectall}'); + + var $insertunorderedlistButton = chrome$(".buttonicon-insertunorderedlist"); + $insertunorderedlistButton.click(); + + var $indentButton = chrome$(".buttonicon-indent"); + $indentButton.click(); // make it indented twice + + expect(inner$("div").first().find(".list-bullet2").length === 1).to.be(true); + var $outdentButton = chrome$(".buttonicon-outdent"); + $outdentButton.click(); // make it deindented to 1 + + helper.waitFor(function(){ + return inner$("div").first().find(".list-bullet1").length === 1; + }).done(done); + }); +}); + diff --git a/tests/frontend/specs/urls_become_clickable.js b/tests/frontend/specs/urls_become_clickable.js index b989717e2..7f5e30679 100644 --- a/tests/frontend/specs/urls_become_clickable.js +++ b/tests/frontend/specs/urls_become_clickable.js @@ -6,16 +6,16 @@ describe("urls", function(){ }); it("when you enter an url, it becomes clickable", function(done) { - var inner$ = helper.padInner$; - var chrome$ = helper.padChrome$; - + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; + //get the first text element out of the inner iframe var firstTextElement = inner$("div").first(); - + // simulate key presses to delete content firstTextElement.sendkeys('{selectall}'); // select all firstTextElement.sendkeys('{del}'); // clear the first line - firstTextElement.sendkeys('http://etherpad.org'); // insert a URL + firstTextElement.sendkeys('https://etherpad.org'); // insert a URL helper.waitFor(function(){ return inner$("div").first().find("a").length === 1; @@ -28,7 +28,7 @@ describe("urls", function(){ //get the first text element out of the inner iframe var firstTextElement = inner$("div").first(); - var url = "http://etherpad.org/!foo"; + var url = "https://etherpad.org/!foo"; // simulate key presses to delete content firstTextElement.sendkeys('{selectall}'); // select all @@ -50,7 +50,7 @@ describe("urls", function(){ //get the first text element out of the inner iframe var firstTextElement = inner$("div").first(); - var url = "http://etherpad.org/"; + var url = "https://etherpad.org/"; // simulate key presses to delete content firstTextElement.sendkeys('{selectall}'); // select all diff --git a/tests/frontend/specs/automatic_reconnect.js b/tests/frontend/specs/xxauto_reconnect.js similarity index 100% rename from tests/frontend/specs/automatic_reconnect.js rename to tests/frontend/specs/xxauto_reconnect.js diff --git a/tests/frontend/travis/remote_runner.js b/tests/frontend/travis/remote_runner.js index 87311d905..0f1ac699f 100644 --- a/tests/frontend/travis/remote_runner.js +++ b/tests/frontend/travis/remote_runner.js @@ -17,95 +17,125 @@ var sauceTestWorker = async.queue(function (testSettings, callback) { testSettings.name = name; testSettings["public"] = true; testSettings["build"] = process.env.GIT_HASH; + testSettings["extendedDebugging"] = true; // console.json can be downloaded via saucelabs, don't know how to print them into output of the tests + testSettings["tunnelIdentifier"] = process.env.TRAVIS_JOB_NUMBER; - browser.init(testSettings).get("http://localhost:9001/tests/frontend/", function(){ - var url = "https://saucelabs.com/jobs/" + browser.sessionID; - console.log("Remote sauce test '" + name + "' started! " + url); + browser.init(testSettings).get("http://localhost:9001/tests/frontend/", function(){ + var url = "https://saucelabs.com/jobs/" + browser.sessionID; + console.log("Remote sauce test '" + name + "' started! " + url); - //tear down the test excecution - var stopSauce = function(success){ - getStatusInterval && clearInterval(getStatusInterval); - clearTimeout(timeout); + //tear down the test excecution + var stopSauce = function(success,timesup){ + clearInterval(getStatusInterval); + clearTimeout(timeout); - browser.quit(); + browser.quit(function(){ + if(!success){ + allTestsPassed = false; + } - if(!success){ - allTestsPassed = false; + // if stopSauce is called via timeout (in contrast to via getStatusInterval) than the log of up to the last + // five seconds may not be available here. It's an error anyway, so don't care about it. + var testResult = knownConsoleText.replace(/\[red\]/g,'\x1B[31m').replace(/\[yellow\]/g,'\x1B[33m') + .replace(/\[green\]/g,'\x1B[32m').replace(/\[clear\]/g, '\x1B[39m'); + testResult = testResult.split("\\n").map(function(line){ + return "[" + testSettings.browserName + " " + testSettings.platform + (testSettings.version === "" ? '' : (" " + testSettings.version)) + "] " + line; + }).join("\n"); + + console.log(testResult); + if (timesup) { + console.log("[" + testSettings.browserName + " " + testSettings.platform + (testSettings.version === "" ? '' : (" " + testSettings.version)) + "] allowed test duration exceeded"); + } + console.log("Remote sauce test '" + name + "' finished! " + url); + + callback(); + }); } - var testResult = knownConsoleText.replace(/\[red\]/g,'\x1B[31m').replace(/\[yellow\]/g,'\x1B[33m') - .replace(/\[green\]/g,'\x1B[32m').replace(/\[clear\]/g, '\x1B[39m'); - testResult = testResult.split("\\n").map(function(line){ - return "[" + testSettings.browserName + (testSettings.version === "" ? '' : (" " + testSettings.version)) + "] " + line; - }).join("\n"); + /** + * timeout if a test hangs or the job exceeds 9.5 minutes + * It's necessary because if travis kills the saucelabs session due to inactivity, we don't get any output + * @todo this should be configured in testSettings, see https://wiki.saucelabs.com/display/DOCS/Test+Configuration+Options#TestConfigurationOptions-Timeouts + */ + var timeout = setTimeout(function(){ + stopSauce(false,true); + }, 570000); // travis timeout is 10 minutes, set this to a slightly lower value - console.log(testResult); - console.log("Remote sauce test '" + name + "' finished! " + url); + var knownConsoleText = ""; + var getStatusInterval = setInterval(function(){ + browser.eval("$('#console').text()", function(err, consoleText){ + if(!consoleText || err){ + return; + } + knownConsoleText = consoleText; - callback(); - } + if(knownConsoleText.indexOf("FINISHED") > 0){ + let match = knownConsoleText.match(/FINISHED.*([0-9]+) tests passed, ([0-9]+) tests failed/); + // finished without failures + if (match[2] && match[2] == '0'){ + stopSauce(true); - //timeout for the case the test hangs - var timeout = setTimeout(function(){ - stopSauce(false); - }, 60000 * 10); + // finished but some tests did not return or some tests failed + } else { + stopSauce(false); + } + } + }); + }, 5000); + }); - var knownConsoleText = ""; - var getStatusInterval = setInterval(function(){ - browser.eval("$('#console').text()", function(err, consoleText){ - if(!consoleText || err){ - return; - } - knownConsoleText = consoleText; +}, 6); //run 6 tests in parrallel - if(knownConsoleText.indexOf("FINISHED") > 0){ - var success = knownConsoleText.indexOf("FAILED") === -1; - stopSauce(success); - } - }); - }, 5000); - }); -}, 5); //run 5 tests in parrallel - -// Firefox +// 1) Firefox on Linux sauceTestWorker.push({ - 'platform' : 'Linux' + 'platform' : 'Windows 7' , 'browserName' : 'firefox' - , 'version' : '' + , 'version' : '52.0' }); -// Chrome +// 2) Chrome on Linux sauceTestWorker.push({ - 'platform' : 'Linux' - , 'browserName' : 'googlechrome' - , 'version' : '' + 'platform' : 'Windows 7' + , 'browserName' : 'chrome' + , 'version' : '55.0' + , 'args' : ['--use-fake-device-for-media-stream'] }); +// 3) Safari on OSX 10.15 +sauceTestWorker.push({ + 'platform' : 'OS X 10.15' + , 'browserName' : 'safari' + , 'version' : '13.1' +}); + +// 4) Safari on OSX 10.14 +sauceTestWorker.push({ + 'platform' : 'OS X 10.14' + , 'browserName' : 'safari' + , 'version' : '12.0' +}); +// IE 10 doesn't appear to be working anyway /* -// IE 8 +// 4) IE 10 on Win 8 sauceTestWorker.push({ - 'platform' : 'Windows 2003' + 'platform' : 'Windows 8' , 'browserName' : 'iexplore' - , 'version' : '8' + , 'version' : '10.0' }); */ - -// IE 9 +// 5) Edge on Win 10 sauceTestWorker.push({ - 'platform' : 'Windows XP' - , 'browserName' : 'iexplore' - , 'version' : '9' + 'platform' : 'Windows 10' + , 'browserName' : 'microsoftedge' + , 'version' : '83.0' +}); +// 6) Firefox on Win 7 +sauceTestWorker.push({ + 'platform' : 'Windows 7' + , 'browserName' : 'firefox' + , 'version' : '78.0' }); -// IE 10 -sauceTestWorker.push({ - 'platform' : 'Windows 2012' - , 'browserName' : 'iexplore' - , 'version' : '10' +sauceTestWorker.drain(function() { + process.exit(allTestsPassed ? 0 : 1); }); - -sauceTestWorker.drain = function() { - setTimeout(function(){ - process.exit(allTestsPassed ? 0 : 1); - }, 3000); -} diff --git a/tests/frontend/travis/runner.sh b/tests/frontend/travis/runner.sh index 1ae5f6ff0..ffc6bbd5b 100755 --- a/tests/frontend/travis/runner.sh +++ b/tests/frontend/travis/runner.sh @@ -1,18 +1,48 @@ -#!/bin/sh +#!/bin/bash +if [ -z "${SAUCE_USERNAME}" ]; then echo "SAUCE_USERNAME is unset - exiting"; exit 1; fi +if [ -z "${SAUCE_ACCESS_KEY}" ]; then echo "SAUCE_ACCESS_KEY is unset - exiting"; exit 1; fi -#Move to the base folder -cd `dirname $0` +# do not continue if there is an error +set -eu -#start Etherpad -../../../bin/run.sh > /dev/null & -sleep 10 +# source: https://stackoverflow.com/questions/59895/get-the-source-directory-of-a-bash-script-from-within-the-script-itself#246128 +MY_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" -#start remote runner +# reliably move to the etherpad base folder before running it +cd "${MY_DIR}/../../../" + +# start Etherpad, assuming all dependencies are already installed. +# +# This is possible because the "install" section of .travis.yml already contains +# a call to bin/installDeps.sh +echo "Running Etherpad directly, assuming bin/installDeps.sh has already been run" +node node_modules/ep_etherpad-lite/node/server.js "${@}" & + +echo "Now I will try for 15 seconds to connect to Etherpad on http://localhost:9001" + +# wait for at most 15 seconds until Etherpad starts accepting connections +# +# modified from: +# https://unix.stackexchange.com/questions/5277/how-do-i-tell-a-script-to-wait-for-a-process-to-start-accepting-requests-on-a-po#349138 +# +(timeout 15 bash -c 'until echo > /dev/tcp/localhost/9001; do sleep 0.5; done') || \ + (echo "Could not connect to Etherpad on http://localhost:9001" ; exit 1) + +echo "Successfully connected to Etherpad on http://localhost:9001" + +# On the Travis VM, remote_runner.js is found at +# /home/travis/build/ether/[secure]/tests/frontend/travis/remote_runner.js +# which is the same directory that contains this script. +# Let's move back there. +# +# Probably remote_runner.js is injected by Saucelabs. +cd "${MY_DIR}" + +# start the remote runner +echo "Now starting the remote runner" node remote_runner.js exit_code=$? -kill $! kill $(cat /tmp/sauce.pid) -sleep 30 -exit $exit_code \ No newline at end of file +exit $exit_code diff --git a/tests/frontend/travis/runnerBackend.sh b/tests/frontend/travis/runnerBackend.sh new file mode 100755 index 000000000..680e96f9e --- /dev/null +++ b/tests/frontend/travis/runnerBackend.sh @@ -0,0 +1,51 @@ +#!/bin/bash + +# do not continue if there is an error +set -u + +# source: https://stackoverflow.com/questions/59895/get-the-source-directory-of-a-bash-script-from-within-the-script-itself#246128 +MY_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" + +# reliably move to the etherpad base folder before running it +cd "${MY_DIR}/../../../" + +# Set soffice to /usr/bin/soffice +sed 's#\"soffice\": null,#\"soffice\":\"/usr/bin/soffice\",#g' settings.json.template > settings.json.soffice + +# Set allowAnyoneToImport to true +sed 's/\"allowAnyoneToImport\": false,/\"allowAnyoneToImport\": true,/g' settings.json.soffice > settings.json.allowImport + +# Set "max": 10 to 100 to not agressively rate limit +sed 's/\"max\": 10/\"max\": 100/g' settings.json.allowImport > settings.json.rateLimit + +# Set "points": 10 to 1000 to not agressively rate limit commits +sed 's/\"points\": 10/\"points\": 1000/g' settings.json.rateLimit > settings.json + +# start Etherpad, assuming all dependencies are already installed. +# +# This is possible because the "install" section of .travis.yml already contains +# a call to bin/installDeps.sh +echo "Running Etherpad directly, assuming bin/installDeps.sh has already been run" +node node_modules/ep_etherpad-lite/node/server.js "${@}" > /dev/null & + +echo "Now I will try for 15 seconds to connect to Etherpad on http://localhost:9001" + +# wait for at most 15 seconds until Etherpad starts accepting connections +# +# modified from: +# https://unix.stackexchange.com/questions/5277/how-do-i-tell-a-script-to-wait-for-a-process-to-start-accepting-requests-on-a-po#349138 +# +(timeout 15 bash -c 'until echo > /dev/tcp/localhost/9001; do sleep 0.5; done') || \ + (echo "Could not connect to Etherpad on http://localhost:9001" ; exit 1) + +echo "Successfully connected to Etherpad on http://localhost:9001" + +# run the backend tests +echo "Now run the backend tests" +cd src + +failed=0 +npm run test || failed=1 +npm run test-contentcollector || failed=1 + +exit $failed diff --git a/tests/frontend/travis/runnerLoadTest.sh b/tests/frontend/travis/runnerLoadTest.sh new file mode 100755 index 000000000..5ac447758 --- /dev/null +++ b/tests/frontend/travis/runnerLoadTest.sh @@ -0,0 +1,51 @@ +#!/bin/bash + +# do not continue if there is an error +set -eu + +# source: https://stackoverflow.com/questions/59895/get-the-source-directory-of-a-bash-script-from-within-the-script-itself#246128 +MY_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" + +# reliably move to the etherpad base folder before running it +cd "${MY_DIR}/../../../" + +# Set "points": 10 to 1000 to not agressively rate limit commits +sed 's/\"points\": 10/\"points\": 1000/g' settings.json.template > settings.json.points +# And enable loadTest +sed 's/\"loadTest\": false,/\"loadTest\": true,/g' settings.json.points > settings.json + +# start Etherpad, assuming all dependencies are already installed. +# +# This is possible because the "install" section of .travis.yml already contains +# a call to bin/installDeps.sh +echo "Running Etherpad directly, assuming bin/installDeps.sh has already been run" + +node node_modules/ep_etherpad-lite/node/server.js "${@}" > /dev/null & + +echo "Now I will try for 15 seconds to connect to Etherpad on http://localhost:9001" + +# wait for at most 15 seconds until Etherpad starts accepting connections +# +# modified from: +# https://unix.stackexchange.com/questions/5277/how-do-i-tell-a-script-to-wait-for-a-process-to-start-accepting-requests-on-a-po#349138 +# +(timeout 15 bash -c 'until echo > /dev/tcp/localhost/9001; do sleep 0.5; done') || \ + (echo "Could not connect to Etherpad on http://localhost:9001" ; exit 1) + +echo "Successfully connected to Etherpad on http://localhost:9001" + +# Build the minified files? +curl http://localhost:9001/p/minifyme -f -s > /dev/null + +# just in case, let's wait for another 10 seconds before going on +sleep 10 + +# run the backend tests +echo "Now run the load tests for 30 seconds and if it stalls before 100 then error" +etherpad-loadtest -d 30 +exit_code=$? + +kill $! +sleep 5 + +exit $exit_code diff --git a/tests/frontend/travis/sauce_tunnel.sh b/tests/frontend/travis/sauce_tunnel.sh index b19268d05..4ab6e81a4 100755 --- a/tests/frontend/travis/sauce_tunnel.sh +++ b/tests/frontend/travis/sauce_tunnel.sh @@ -1,11 +1,20 @@ #!/bin/bash # download and unzip the sauce connector -curl https://saucelabs.com/downloads/sc-latest-linux.tar.gz > /tmp/sauce.tar.gz +# +# ACHTUNG: as of 2019-12-21, downloading sc-latest-linux.tar.gz does not work. +# It is necessary to explicitly download a specific version, for +# example https://saucelabs.com/downloads/sc-4.5.4-linux.tar.gz +# Supported versions are currently listed at: +# https://wiki.saucelabs.com/display/DOCS/Downloading+Sauce+Connect+Proxy +if [ -z "${SAUCE_USERNAME}" ]; then echo "SAUCE_USERNAME is unset - exiting"; exit 1; fi +if [ -z "${SAUCE_ACCESS_KEY}" ]; then echo "SAUCE_ACCESS_KEY is unset - exiting"; exit 1; fi + +curl https://saucelabs.com/downloads/sc-4.6.2-linux.tar.gz > /tmp/sauce.tar.gz tar zxf /tmp/sauce.tar.gz --directory /tmp mv /tmp/sc-*-linux /tmp/sauce_connect # start the sauce connector in background and make sure it doesn't output the secret key -(/tmp/sauce_connect/bin/sc --user $SAUCE_USERNAME --key $SAUCE_ACCESS_KEY --pidfile /tmp/sauce.pid --readyfile /tmp/tunnel > /dev/null )& +(/tmp/sauce_connect/bin/sc --user "${SAUCE_USERNAME}" --key "${SAUCE_ACCESS_KEY}" -i "${TRAVIS_JOB_NUMBER}" --pidfile /tmp/sauce.pid --readyfile /tmp/tunnel > /dev/null )& # wait for the tunnel to build up while [ ! -e "/tmp/tunnel" ]