diff --git a/services/web/Dockerfile.frontend b/services/web/Dockerfile.frontend new file mode 100644 index 0000000000..529a4f1f9a --- /dev/null +++ b/services/web/Dockerfile.frontend @@ -0,0 +1,6 @@ +FROM node:8.9.4 + +# Install Google Chrome +RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - +RUN sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' +RUN apt-get update && apt-get install -y google-chrome-stable diff --git a/services/web/Jenkinsfile b/services/web/Jenkinsfile index 6d32c863cf..7734de259a 100644 --- a/services/web/Jenkinsfile +++ b/services/web/Jenkinsfile @@ -63,7 +63,14 @@ pipeline { } } steps { - sh 'make --no-print-directory test_unit test_frontend MOCHA_ARGS="--reporter tap"' + sh 'make --no-print-directory test_unit MOCHA_ARGS="--reporter tap"' + } + } + + stage('Frontend Unit Test') { + steps { + // Spawns its own docker containers + sh 'make --no-print-directory test_frontend' } } diff --git a/services/web/Makefile b/services/web/Makefile index e9c796b6a0..76d8891dd7 100644 --- a/services/web/Makefile +++ b/services/web/Makefile @@ -159,6 +159,7 @@ clean_frontend: clean_tests: rm -rf test/unit/js + rm -rf test/unit_frontend/js rm -rf test/acceptance/js clean_modules: @@ -181,8 +182,9 @@ test: test_unit test_frontend test_acceptance test_unit: npm -q run test:unit -- ${MOCHA_ARGS} -test_frontend: - npm -q run test:frontend -- ${MOCHA_ARGS} +test_frontend: test_clean # stop service + $(MAKE) compile + docker-compose ${DOCKER_COMPOSE_FLAGS} up test_frontend test_acceptance: test_acceptance_app test_acceptance_modules diff --git a/services/web/bin/frontend_test b/services/web/bin/frontend_test deleted file mode 100755 index 599055803a..0000000000 --- a/services/web/bin/frontend_test +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -set -e; -MOCHA="node_modules/.bin/mocha --recursive --reporter spec" -$MOCHA "$@" test/unit_frontend/js - diff --git a/services/web/docker-compose.yml b/services/web/docker-compose.yml index 930d3cdc02..5862790191 100644 --- a/services/web/docker-compose.yml +++ b/services/web/docker-compose.yml @@ -20,6 +20,15 @@ services: - mongo command: node app.js + test_frontend: + build: + context: . + dockerfile: Dockerfile.frontend + volumes: + - .:/app + working_dir: /app + command: npm run test:frontend + redis: image: redis diff --git a/services/web/karma.conf.js b/services/web/karma.conf.js new file mode 100644 index 0000000000..9c6b38e659 --- /dev/null +++ b/services/web/karma.conf.js @@ -0,0 +1,36 @@ +module.exports = function (config) { + config.set({ + customLaunchers: { + ChromeCustom: { + base: 'ChromeHeadless', + // We must disable the Chrome sandbox when running Chrome inside Docker + // (Chrome's sandbox needs more permissions than Docker allows by + // default) + flags: ['--no-sandbox'] + } + }, + browsers: ['ChromeCustom'], + files: [ + 'test/unit_frontend/js/bootstrap.js', + // Angular must be loaded before requirejs to set up angular global + 'public/js/libs/angular-1.6.4.min.js', + 'public/js/libs/angular-mocks.js', + 'public/js/libs/jquery-1.11.1.min.js', + // Set up requirejs + 'test/unit_frontend/js/test-main.js', + // Include source & test files, but don't "include" them as requirejs + // handles this for us + { pattern: 'public/js/**/*.js', included: false }, + { pattern: 'test/unit_frontend/js/**/*.js', included: false } + ], + frameworks: ['requirejs', 'mocha', 'chai-sinon'], + plugins: [ + require('karma-requirejs'), + require('karma-mocha'), + require('karma-chai-sinon'), + require('karma-chrome-launcher'), + require('karma-tap-reporter') + ], + reporters: ['tap'] + }); +} diff --git a/services/web/npm-shrinkwrap.json b/services/web/npm-shrinkwrap.json index cccbf5acb5..d92d50b809 100644 --- a/services/web/npm-shrinkwrap.json +++ b/services/web/npm-shrinkwrap.json @@ -48,11 +48,45 @@ } } }, + "acorn-node": { + "version": "1.3.0", + "from": "acorn-node@>=1.2.0 <2.0.0", + "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.3.0.tgz", + "dev": true, + "dependencies": { + "acorn": { + "version": "5.4.1", + "from": "acorn@>=5.4.1 <6.0.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.4.1.tgz", + "dev": true + } + } + }, "addressparser": { "version": "0.2.1", "from": "addressparser@>=0.2.0 <0.3.0", "resolved": "https://registry.npmjs.org/addressparser/-/addressparser-0.2.1.tgz" }, + "after": { + "version": "0.8.2", + "from": "after@0.8.2", + "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", + "dev": true + }, + "agent-base": { + "version": "2.1.1", + "from": "agent-base@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-2.1.1.tgz", + "dev": true, + "dependencies": { + "semver": { + "version": "5.0.3", + "from": "semver@>=5.0.1 <5.1.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.0.3.tgz", + "dev": true + } + } + }, "ajv": { "version": "5.5.2", "from": "ajv@>=5.1.0 <6.0.0", @@ -74,6 +108,29 @@ "from": "amdefine@>=0.0.4", "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz" }, + "amqplib": { + "version": "0.5.2", + "from": "amqplib@>=0.5.2 <0.6.0", + "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.5.2.tgz", + "dev": true, + "optional": true, + "dependencies": { + "bluebird": { + "version": "3.5.1", + "from": "bluebird@^3.4.6", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", + "dev": true, + "optional": true + }, + "readable-stream": { + "version": "1.1.14", + "from": "readable-stream@>=1.0.0 <2.0.0 >=1.1.9", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "dev": true, + "optional": true + } + } + }, "ansi-align": { "version": "2.0.0", "from": "ansi-align@>=2.0.0 <3.0.0", @@ -203,6 +260,12 @@ "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", "dev": true }, + "array-filter": { + "version": "0.0.1", + "from": "array-filter@>=0.0.0 <0.1.0", + "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz", + "dev": true + }, "array-find-index": { "version": "1.0.2", "from": "array-find-index@>=1.0.1 <2.0.0", @@ -220,6 +283,24 @@ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", "dev": true }, + "array-map": { + "version": "0.0.0", + "from": "array-map@>=0.0.0 <0.1.0", + "resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz", + "dev": true + }, + "array-reduce": { + "version": "0.0.0", + "from": "array-reduce@>=0.0.0 <0.1.0", + "resolved": "https://registry.npmjs.org/array-reduce/-/array-reduce-0.0.0.tgz", + "dev": true + }, + "array-slice": { + "version": "0.2.3", + "from": "array-slice@>=0.2.3 <0.3.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", + "dev": true + }, "array-union": { "version": "1.0.2", "from": "array-union@>=1.0.1 <2.0.0", @@ -238,6 +319,12 @@ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", "dev": true }, + "arraybuffer.slice": { + "version": "0.0.7", + "from": "arraybuffer.slice@>=0.0.7 <0.1.0", + "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", + "dev": true + }, "asap": { "version": "2.0.6", "from": "asap@>=2.0.3 <2.1.0", @@ -276,6 +363,27 @@ "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", "dev": true }, + "ast-types": { + "version": "0.10.2", + "from": "ast-types@>=0.0.0 <1.0.0", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.10.2.tgz", + "dev": true, + "optional": true + }, + "astw": { + "version": "2.2.0", + "from": "astw@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/astw/-/astw-2.2.0.tgz", + "dev": true, + "dependencies": { + "acorn": { + "version": "4.0.13", + "from": "acorn@>=4.0.3 <5.0.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "dev": true + } + } + }, "async": { "version": "0.6.2", "from": "async@0.6.2", @@ -287,6 +395,12 @@ "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", "dev": true }, + "async-limiter": { + "version": "1.0.0", + "from": "async-limiter@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "dev": true + }, "asynckit": { "version": "0.4.0", "from": "asynckit@>=0.4.0 <0.5.0", @@ -331,6 +445,13 @@ "from": "aws4@>=1.6.0 <2.0.0", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz" }, + "axios": { + "version": "0.15.3", + "from": "axios@>=0.15.3 <0.16.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.15.3.tgz", + "dev": true, + "optional": true + }, "axo": { "version": "0.0.2", "from": "axo@>=0.0.0 <0.1.0", @@ -710,6 +831,12 @@ "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", "dev": true }, + "backo2": { + "version": "1.0.2", + "from": "backo2@1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "dev": true + }, "backoff": { "version": "2.5.0", "from": "backoff@>=2.5.0 <3.0.0", @@ -726,6 +853,12 @@ "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", "dev": true }, + "base64-arraybuffer": { + "version": "0.1.5", + "from": "base64-arraybuffer@0.1.5", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", + "dev": true + }, "base64-js": { "version": "1.2.1", "from": "base64-js@>=1.0.2 <2.0.0", @@ -753,6 +886,12 @@ } } }, + "base64id": { + "version": "1.0.0", + "from": "base64id@1.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", + "dev": true + }, "base64url": { "version": "2.0.0", "from": "base64url@>=2.0.0 <3.0.0", @@ -785,6 +924,12 @@ "from": "bcryptjs@2.3.0", "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.3.0.tgz" }, + "better-assert": { + "version": "1.0.2", + "from": "better-assert@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", + "dev": true + }, "big.js": { "version": "3.2.0", "from": "big.js@>=3.1.3 <4.0.0", @@ -802,11 +947,24 @@ "from": "bindings@1.2.1", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz" }, + "bitsyntax": { + "version": "0.0.4", + "from": "bitsyntax@>=0.0.4 <0.1.0", + "resolved": "https://registry.npmjs.org/bitsyntax/-/bitsyntax-0.0.4.tgz", + "dev": true, + "optional": true + }, "bl": { "version": "0.6.0", "from": "bl@>=0.6.0 <0.7.0", "resolved": "https://registry.npmjs.org/bl/-/bl-0.6.0.tgz" }, + "blob": { + "version": "0.0.4", + "from": "blob@0.0.4", + "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz", + "dev": true + }, "block-stream": { "version": "0.0.9", "from": "block-stream@*", @@ -964,6 +1122,102 @@ "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", "dev": true }, + "browser-pack": { + "version": "6.0.4", + "from": "browser-pack@>=6.0.1 <7.0.0", + "resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.0.4.tgz", + "dev": true + }, + "browser-resolve": { + "version": "1.11.2", + "from": "browser-resolve@>=1.11.0 <2.0.0", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.2.tgz", + "dev": true, + "dependencies": { + "resolve": { + "version": "1.1.7", + "from": "resolve@1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "dev": true + } + } + }, + "browserify": { + "version": "14.5.0", + "from": "browserify@>=14.5.0 <15.0.0", + "resolved": "https://registry.npmjs.org/browserify/-/browserify-14.5.0.tgz", + "dev": true, + "dependencies": { + "buffer": { + "version": "5.0.8", + "from": "buffer@>=5.0.2 <6.0.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.0.8.tgz", + "dev": true + }, + "domain-browser": { + "version": "1.1.7", + "from": "domain-browser@>=1.1.0 <1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz", + "dev": true + }, + "glob": { + "version": "7.1.2", + "from": "glob@^7.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "from": "isarray@~1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "from": "minimatch@^3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.0", + "from": "process-nextick-args@~2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "dev": true + }, + "readable-stream": { + "version": "2.3.4", + "from": "readable-stream@>=2.0.2 <3.0.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", + "dev": true + }, + "string_decoder": { + "version": "1.0.3", + "from": "string_decoder@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "dev": true + }, + "timers-browserify": { + "version": "1.4.2", + "from": "timers-browserify@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz", + "dev": true + }, + "url": { + "version": "0.11.0", + "from": "url@>=0.11.0 <0.12.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "dev": true, + "dependencies": { + "punycode": { + "version": "1.3.2", + "from": "punycode@1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "dev": true + } + } + } + } + }, "browserify-aes": { "version": "1.1.1", "from": "browserify-aes@>=1.0.4 <2.0.0", @@ -1039,6 +1293,12 @@ "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", "dev": true }, + "buffer-more-ints": { + "version": "0.0.2", + "from": "buffer-more-ints@0.0.2", + "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-0.0.2.tgz", + "dev": true + }, "buffer-shims": { "version": "1.0.0", "from": "buffer-shims@>=1.0.0 <2.0.0", @@ -1107,6 +1367,18 @@ "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", "dev": true }, + "cached-path-relative": { + "version": "1.0.1", + "from": "cached-path-relative@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.0.1.tgz", + "dev": true + }, + "callsite": { + "version": "1.0.0", + "from": "callsite@1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "dev": true + }, "camelcase": { "version": "1.2.1", "from": "camelcase@>=1.0.2 <2.0.0", @@ -1161,13 +1433,13 @@ }, "chai": { "version": "3.5.0", - "from": "chai@3.5.0", + "from": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", "dev": true }, "chai-spies": { "version": "1.0.0", - "from": "chai-spies@latest", + "from": "chai-spies@", "resolved": "https://registry.npmjs.org/chai-spies/-/chai-spies-1.0.0.tgz" }, "chalk": { @@ -1238,6 +1510,12 @@ "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", "dev": true }, + "circular-json": { + "version": "0.5.1", + "from": "circular-json@>=0.5.1 <0.6.0", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.5.1.tgz", + "dev": true + }, "class-utils": { "version": "0.3.6", "from": "class-utils@>=0.3.5 <0.4.0", @@ -1387,6 +1665,32 @@ "from": "colors@>=0.6.2 <0.7.0", "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz" }, + "combine-lists": { + "version": "1.0.1", + "from": "combine-lists@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/combine-lists/-/combine-lists-1.0.1.tgz", + "dev": true + }, + "combine-source-map": { + "version": "0.8.0", + "from": "combine-source-map@>=0.8.0 <0.9.0", + "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz", + "dev": true, + "dependencies": { + "convert-source-map": { + "version": "1.1.3", + "from": "convert-source-map@>=1.1.0 <1.2.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "from": "source-map@>=0.5.3 <0.6.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "dev": true + } + } + }, "combined-stream": { "version": "1.0.5", "from": "combined-stream@>=1.0.5 <1.1.0", @@ -1403,12 +1707,24 @@ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", "dev": true }, + "component-bind": { + "version": "1.0.0", + "from": "component-bind@1.0.0", + "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", + "dev": true + }, "component-emitter": { "version": "1.2.1", "from": "component-emitter@>=1.2.1 <2.0.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", "dev": true }, + "component-inherit": { + "version": "0.0.3", + "from": "component-inherit@0.0.3", + "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", + "dev": true + }, "compressible": { "version": "2.0.12", "from": "compressible@>=2.0.11 <2.1.0", @@ -1452,12 +1768,70 @@ "from": "concat-map@0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" }, + "concat-stream": { + "version": "1.5.2", + "from": "concat-stream@>=1.5.1 <1.6.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz", + "dev": true, + "dependencies": { + "isarray": { + "version": "1.0.0", + "from": "isarray@~1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "dev": true + }, + "readable-stream": { + "version": "2.0.6", + "from": "readable-stream@>=2.0.0 <2.1.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "dev": true + } + } + }, "configstore": { "version": "3.1.1", "from": "configstore@>=3.0.0 <4.0.0", "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.1.tgz", "dev": true }, + "connect": { + "version": "3.6.5", + "from": "connect@>=3.6.0 <4.0.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.6.5.tgz", + "dev": true, + "dependencies": { + "debug": { + "version": "2.6.9", + "from": "debug@2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "dev": true + }, + "escape-html": { + "version": "1.0.3", + "from": "escape-html@>=1.0.3 <1.1.0", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "dev": true + }, + "finalhandler": { + "version": "1.0.6", + "from": "finalhandler@1.0.6", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.6.tgz", + "dev": true + }, + "statuses": { + "version": "1.3.1", + "from": "statuses@>=1.3.1 <1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "dev": true + }, + "utils-merge": { + "version": "1.0.1", + "from": "utils-merge@1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "dev": true + } + } + }, "connect-history-api-fallback": { "version": "1.5.0", "from": "connect-history-api-fallback@>=1.3.0 <2.0.0", @@ -1697,6 +2071,12 @@ "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", "dev": true }, + "custom-event": { + "version": "1.0.1", + "from": "custom-event@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "dev": true + }, "cycle": { "version": "1.0.3", "from": "cycle@>=1.0.0 <1.1.0", @@ -1719,6 +2099,19 @@ "from": "dasherize@2.0.0", "resolved": "https://registry.npmjs.org/dasherize/-/dasherize-2.0.0.tgz" }, + "data-uri-to-buffer": { + "version": "1.2.0", + "from": "data-uri-to-buffer@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz", + "dev": true, + "optional": true + }, + "date-format": { + "version": "1.2.0", + "from": "date-format@>=1.2.0 <2.0.0", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-1.2.0.tgz", + "dev": true + }, "date-now": { "version": "0.1.4", "from": "date-now@>=0.1.4 <0.2.0", @@ -1771,6 +2164,13 @@ "from": "deep-extend@>=0.4.0 <0.5.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz" }, + "deep-is": { + "version": "0.1.3", + "from": "deep-is@>=0.1.3 <0.2.0", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "dev": true, + "optional": true + }, "define-properties": { "version": "1.1.2", "from": "define-properties@>=1.1.2 <2.0.0", @@ -1783,11 +2183,33 @@ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", "dev": true }, + "defined": { + "version": "1.0.0", + "from": "defined@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "dev": true + }, "deflate-crc32-stream": { "version": "0.1.2", "from": "deflate-crc32-stream@>=0.1.0 <0.2.0", "resolved": "https://registry.npmjs.org/deflate-crc32-stream/-/deflate-crc32-stream-0.1.2.tgz" }, + "degenerator": { + "version": "1.0.4", + "from": "degenerator@>=1.0.2 <1.1.0", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-1.0.4.tgz", + "dev": true, + "optional": true, + "dependencies": { + "esprima": { + "version": "3.1.3", + "from": "esprima@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "dev": true, + "optional": true + } + } + }, "del": { "version": "3.0.0", "from": "del@>=3.0.0 <4.0.0", @@ -1834,6 +2256,12 @@ "from": "depd@>=1.1.1 <1.2.0", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz" }, + "deps-sort": { + "version": "2.0.0", + "from": "deps-sort@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/deps-sort/-/deps-sort-2.0.0.tgz", + "dev": true + }, "des.js": { "version": "1.0.0", "from": "des.js@>=1.0.0 <2.0.0", @@ -1862,6 +2290,26 @@ "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.3.tgz", "dev": true }, + "detective": { + "version": "4.7.1", + "from": "detective@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/detective/-/detective-4.7.1.tgz", + "dev": true, + "dependencies": { + "acorn": { + "version": "5.4.1", + "from": "acorn@>=5.2.1 <6.0.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.4.1.tgz", + "dev": true + } + } + }, + "di": { + "version": "0.0.1", + "from": "di@>=0.0.1 <0.0.2", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "dev": true + }, "dicer": { "version": "0.2.5", "from": "dicer@0.2.5", @@ -1919,6 +2367,12 @@ "from": "doctypes@>=1.1.0 <2.0.0", "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz" }, + "dom-serialize": { + "version": "2.2.1", + "from": "dom-serialize@>=2.2.0 <3.0.0", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "dev": true + }, "dom-serializer": { "version": "0.1.0", "from": "dom-serializer@>=0.0.0 <1.0.0", @@ -1990,6 +2444,38 @@ "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", "dev": true }, + "duplexer2": { + "version": "0.1.4", + "from": "duplexer2@>=0.1.2 <0.2.0", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "dev": true, + "dependencies": { + "isarray": { + "version": "1.0.0", + "from": "isarray@~1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.0", + "from": "process-nextick-args@~2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "dev": true + }, + "readable-stream": { + "version": "2.3.4", + "from": "readable-stream@^2.0.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", + "dev": true + }, + "string_decoder": { + "version": "1.0.3", + "from": "string_decoder@~1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "dev": true + } + } + }, "duplexer3": { "version": "0.1.4", "from": "duplexer3@>=0.1.4 <0.2.0", @@ -2063,12 +2549,70 @@ "from": "end-of-stream@>=0.1.3 <0.2.0", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-0.1.5.tgz" }, + "engine.io": { + "version": "3.1.4", + "from": "engine.io@>=3.1.0 <3.2.0", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.1.4.tgz", + "dev": true, + "dependencies": { + "accepts": { + "version": "1.3.3", + "from": "accepts@1.3.3", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz", + "dev": true + }, + "cookie": { + "version": "0.3.1", + "from": "cookie@0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "dev": true + }, + "debug": { + "version": "2.6.9", + "from": "debug@~2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "dev": true + }, + "negotiator": { + "version": "0.6.1", + "from": "negotiator@0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "dev": true + } + } + }, + "engine.io-client": { + "version": "3.1.4", + "from": "engine.io-client@>=3.1.0 <3.2.0", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.1.4.tgz", + "dev": true, + "dependencies": { + "debug": { + "version": "2.6.9", + "from": "debug@~2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "dev": true + } + } + }, + "engine.io-parser": { + "version": "2.1.2", + "from": "engine.io-parser@>=2.1.0 <2.2.0", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.2.tgz", + "dev": true + }, "enhanced-resolve": { "version": "3.4.1", "from": "enhanced-resolve@>=3.4.0 <4.0.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz", "dev": true }, + "ent": { + "version": "2.2.0", + "from": "ent@>=2.2.0 <2.3.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "dev": true + }, "entities": { "version": "1.1.1", "from": "entities@>=1.1.1 <2.0.0", @@ -2150,6 +2694,29 @@ "from": "escape-string-regexp@^1.0.2", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" }, + "escodegen": { + "version": "1.9.0", + "from": "escodegen@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.9.0.tgz", + "dev": true, + "optional": true, + "dependencies": { + "esprima": { + "version": "3.1.3", + "from": "esprima@^3.1.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "dev": true, + "optional": true + }, + "source-map": { + "version": "0.5.7", + "from": "source-map@>=0.5.6 <0.6.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "dev": true, + "optional": true + } + } + }, "escope": { "version": "3.6.0", "from": "escope@>=3.6.0 <4.0.0", @@ -2242,6 +2809,44 @@ "from": "exit@>=0.1.1 <0.2.0", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz" }, + "expand-braces": { + "version": "0.1.2", + "from": "expand-braces@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/expand-braces/-/expand-braces-0.1.2.tgz", + "dev": true, + "dependencies": { + "array-unique": { + "version": "0.2.1", + "from": "array-unique@^0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "dev": true + }, + "braces": { + "version": "0.1.5", + "from": "braces@>=0.1.2 <0.2.0", + "resolved": "https://registry.npmjs.org/braces/-/braces-0.1.5.tgz", + "dev": true + }, + "expand-range": { + "version": "0.1.1", + "from": "expand-range@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-0.1.1.tgz", + "dev": true + }, + "is-number": { + "version": "0.1.1", + "from": "is-number@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-0.1.1.tgz", + "dev": true + }, + "repeat-string": { + "version": "0.2.2", + "from": "repeat-string@>=0.2.2 <0.3.0", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-0.2.2.tgz", + "dev": true + } + } + }, "expand-brackets": { "version": "2.1.4", "from": "expand-brackets@>=2.1.4 <3.0.0", @@ -2458,6 +3063,13 @@ "from": "fast-json-stable-stringify@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz" }, + "fast-levenshtein": { + "version": "2.0.6", + "from": "fast-levenshtein@>=2.0.4 <2.1.0", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "dev": true, + "optional": true + }, "faye-websocket": { "version": "0.10.0", "from": "faye-websocket@>=0.10.0 <0.11.0", @@ -2469,6 +3081,13 @@ "from": "fd-slicer@>=1.0.1 <1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz" }, + "file-uri-to-path": { + "version": "1.0.0", + "from": "file-uri-to-path@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "dev": true, + "optional": true + }, "file-utils": { "version": "0.1.5", "from": "file-utils@>=0.1.5 <0.2.0", @@ -2553,6 +3172,22 @@ "from": "flexbuffer@0.0.6", "resolved": "https://registry.npmjs.org/flexbuffer/-/flexbuffer-0.0.6.tgz" }, + "follow-redirects": { + "version": "1.0.0", + "from": "follow-redirects@1.0.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.0.0.tgz", + "dev": true, + "optional": true, + "dependencies": { + "debug": { + "version": "2.6.9", + "from": "debug@^2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "dev": true, + "optional": true + } + } + }, "for-in": { "version": "1.0.2", "from": "for-in@>=1.0.2 <2.0.0", @@ -2634,6 +3269,12 @@ "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", "dev": true }, + "fs-access": { + "version": "1.0.1", + "from": "fs-access@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", + "dev": true + }, "fs-extra": { "version": "4.0.3", "from": "fs-extra@>=4.0.2 <5.0.0", @@ -2644,6 +3285,780 @@ "from": "fs.realpath@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" }, + "fsevents": { + "version": "1.1.3", + "from": "fsevents@1.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.3.tgz", + "dev": true, + "optional": true, + "dependencies": { + "abbrev": { + "version": "1.1.0", + "from": "abbrev@1.1.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz", + "dev": true, + "optional": true + }, + "ajv": { + "version": "4.11.8", + "from": "ajv@4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "from": "ansi-regex@2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "dev": true + }, + "aproba": { + "version": "1.1.1", + "from": "aproba@1.1.1", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.1.1.tgz", + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "from": "are-we-there-yet@1.1.4", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", + "dev": true, + "optional": true + }, + "asn1": { + "version": "0.2.3", + "from": "asn1@0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "dev": true, + "optional": true + }, + "assert-plus": { + "version": "0.2.0", + "from": "assert-plus@0.2.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "dev": true, + "optional": true + }, + "asynckit": { + "version": "0.4.0", + "from": "asynckit@0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "dev": true, + "optional": true + }, + "aws-sign2": { + "version": "0.6.0", + "from": "aws-sign2@0.6.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "dev": true, + "optional": true + }, + "aws4": { + "version": "1.6.0", + "from": "aws4@1.6.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "dev": true, + "optional": true + }, + "balanced-match": { + "version": "0.4.2", + "from": "balanced-match@0.4.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "from": "bcrypt-pbkdf@1.0.1", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "dev": true, + "optional": true + }, + "block-stream": { + "version": "0.0.9", + "from": "block-stream@0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "dev": true + }, + "boom": { + "version": "2.10.1", + "from": "boom@2.10.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "dev": true + }, + "brace-expansion": { + "version": "1.1.7", + "from": "brace-expansion@1.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.7.tgz", + "dev": true + }, + "buffer-shims": { + "version": "1.0.0", + "from": "buffer-shims@1.0.0", + "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "from": "caseless@0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "dev": true, + "optional": true + }, + "co": { + "version": "4.6.0", + "from": "co@4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "from": "code-point-at@1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "dev": true + }, + "combined-stream": { + "version": "1.0.5", + "from": "combined-stream@1.0.5", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "from": "concat-map@0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "from": "console-control-strings@1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "from": "core-util-is@1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "dev": true + }, + "cryptiles": { + "version": "2.0.5", + "from": "cryptiles@2.0.5", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "dev": true + }, + "dashdash": { + "version": "1.14.1", + "from": "dashdash@1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "dev": true, + "optional": true, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "from": "assert-plus@1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "dev": true, + "optional": true + } + } + }, + "debug": { + "version": "2.6.8", + "from": "debug@2.6.8", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "dev": true, + "optional": true + }, + "deep-extend": { + "version": "0.4.2", + "from": "deep-extend@0.4.2", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", + "dev": true, + "optional": true + }, + "delayed-stream": { + "version": "1.0.0", + "from": "delayed-stream@1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "dev": true + }, + "delegates": { + "version": "1.0.0", + "from": "delegates@1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.2", + "from": "detect-libc@1.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.2.tgz", + "dev": true, + "optional": true + }, + "ecc-jsbn": { + "version": "0.1.1", + "from": "ecc-jsbn@0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "dev": true, + "optional": true + }, + "extend": { + "version": "3.0.1", + "from": "extend@3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "dev": true, + "optional": true + }, + "extsprintf": { + "version": "1.0.2", + "from": "extsprintf@1.0.2", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz", + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "from": "forever-agent@0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "dev": true, + "optional": true + }, + "form-data": { + "version": "2.1.4", + "from": "form-data@2.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "dev": true, + "optional": true + }, + "fs.realpath": { + "version": "1.0.0", + "from": "fs.realpath@1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "dev": true + }, + "fstream": { + "version": "1.0.11", + "from": "fstream@1.0.11", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", + "dev": true + }, + "fstream-ignore": { + "version": "1.0.5", + "from": "fstream-ignore@1.0.5", + "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz", + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "from": "gauge@2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "dev": true, + "optional": true + }, + "getpass": { + "version": "0.1.7", + "from": "getpass@0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "dev": true, + "optional": true, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "from": "assert-plus@1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "dev": true, + "optional": true + } + } + }, + "glob": { + "version": "7.1.2", + "from": "glob@7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "dev": true + }, + "graceful-fs": { + "version": "4.1.11", + "from": "graceful-fs@4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "dev": true + }, + "har-schema": { + "version": "1.0.5", + "from": "har-schema@1.0.5", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", + "dev": true, + "optional": true + }, + "har-validator": { + "version": "4.2.1", + "from": "har-validator@4.2.1", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", + "dev": true, + "optional": true + }, + "has-unicode": { + "version": "2.0.1", + "from": "has-unicode@2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "dev": true, + "optional": true + }, + "hawk": { + "version": "3.1.3", + "from": "hawk@3.1.3", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "dev": true + }, + "hoek": { + "version": "2.16.3", + "from": "hoek@2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "dev": true + }, + "http-signature": { + "version": "1.1.1", + "from": "http-signature@1.1.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "dev": true, + "optional": true + }, + "inflight": { + "version": "1.0.6", + "from": "inflight@1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "dev": true + }, + "inherits": { + "version": "2.0.3", + "from": "inherits@2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "dev": true + }, + "ini": { + "version": "1.3.4", + "from": "ini@1.3.4", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "from": "is-fullwidth-code-point@1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "from": "is-typedarray@1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "dev": true, + "optional": true + }, + "isarray": { + "version": "1.0.0", + "from": "isarray@1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "from": "isstream@0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "dev": true, + "optional": true + }, + "jodid25519": { + "version": "1.0.2", + "from": "jodid25519@1.0.2", + "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz", + "dev": true, + "optional": true + }, + "jsbn": { + "version": "0.1.1", + "from": "jsbn@0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "dev": true, + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "from": "json-schema@0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "dev": true, + "optional": true + }, + "json-stable-stringify": { + "version": "1.0.1", + "from": "json-stable-stringify@1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "dev": true, + "optional": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "from": "json-stringify-safe@5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "dev": true, + "optional": true + }, + "jsonify": { + "version": "0.0.0", + "from": "jsonify@0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "dev": true, + "optional": true + }, + "jsprim": { + "version": "1.4.0", + "from": "jsprim@1.4.0", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz", + "dev": true, + "optional": true, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "from": "assert-plus@1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "dev": true, + "optional": true + } + } + }, + "mime-db": { + "version": "1.27.0", + "from": "mime-db@1.27.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", + "dev": true + }, + "mime-types": { + "version": "2.1.15", + "from": "mime-types@2.1.15", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "from": "minimatch@3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "dev": true + }, + "minimist": { + "version": "0.0.8", + "from": "minimist@0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "from": "mkdirp@0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "dev": true + }, + "ms": { + "version": "2.0.0", + "from": "ms@2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "dev": true, + "optional": true + }, + "node-pre-gyp": { + "version": "0.6.39", + "from": "node-pre-gyp@0.6.39", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz", + "dev": true, + "optional": true + }, + "nopt": { + "version": "4.0.1", + "from": "nopt@4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "dev": true, + "optional": true + }, + "npmlog": { + "version": "4.1.0", + "from": "npmlog@4.1.0", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.0.tgz", + "dev": true, + "optional": true + }, + "number-is-nan": { + "version": "1.0.1", + "from": "number-is-nan@1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "dev": true + }, + "oauth-sign": { + "version": "0.8.2", + "from": "oauth-sign@0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "dev": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "from": "object-assign@4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "from": "once@1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "dev": true + }, + "os-homedir": { + "version": "1.0.2", + "from": "os-homedir@1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "from": "os-tmpdir@1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.4", + "from": "osenv@0.1.4", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz", + "dev": true, + "optional": true + }, + "path-is-absolute": { + "version": "1.0.1", + "from": "path-is-absolute@1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "dev": true + }, + "performance-now": { + "version": "0.2.0", + "from": "performance-now@0.2.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "1.0.7", + "from": "process-nextick-args@1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "dev": true + }, + "punycode": { + "version": "1.4.1", + "from": "punycode@1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "dev": true, + "optional": true + }, + "qs": { + "version": "6.4.0", + "from": "qs@6.4.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.1", + "from": "rc@1.2.1", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.1.tgz", + "dev": true, + "optional": true, + "dependencies": { + "minimist": { + "version": "1.2.0", + "from": "minimist@1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.2.9", + "from": "readable-stream@2.2.9", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.9.tgz", + "dev": true + }, + "request": { + "version": "2.81.0", + "from": "request@2.81.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", + "dev": true, + "optional": true + }, + "rimraf": { + "version": "2.6.1", + "from": "rimraf@2.6.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz", + "dev": true + }, + "safe-buffer": { + "version": "5.0.1", + "from": "safe-buffer@5.0.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz", + "dev": true + }, + "semver": { + "version": "5.3.0", + "from": "semver@5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "from": "set-blocking@2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "from": "signal-exit@3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "dev": true, + "optional": true + }, + "sntp": { + "version": "1.0.9", + "from": "sntp@1.0.9", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "dev": true + }, + "sshpk": { + "version": "1.13.0", + "from": "sshpk@1.13.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.0.tgz", + "dev": true, + "optional": true, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "from": "assert-plus@1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "dev": true, + "optional": true + } + } + }, + "string_decoder": { + "version": "1.0.1", + "from": "string_decoder@1.0.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.1.tgz", + "dev": true + }, + "string-width": { + "version": "1.0.2", + "from": "string-width@1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "dev": true + }, + "stringstream": { + "version": "0.0.5", + "from": "stringstream@0.0.5", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "dev": true, + "optional": true + }, + "strip-ansi": { + "version": "3.0.1", + "from": "strip-ansi@3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "from": "strip-json-comments@2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "dev": true, + "optional": true + }, + "tar": { + "version": "2.2.1", + "from": "tar@2.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "dev": true + }, + "tar-pack": { + "version": "3.4.0", + "from": "tar-pack@3.4.0", + "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.0.tgz", + "dev": true, + "optional": true + }, + "tough-cookie": { + "version": "2.3.2", + "from": "tough-cookie@2.3.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", + "dev": true, + "optional": true + }, + "tunnel-agent": { + "version": "0.6.0", + "from": "tunnel-agent@0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "dev": true, + "optional": true + }, + "tweetnacl": { + "version": "0.14.5", + "from": "tweetnacl@0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "dev": true, + "optional": true + }, + "uid-number": { + "version": "0.0.6", + "from": "uid-number@0.0.6", + "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", + "dev": true, + "optional": true + }, + "util-deprecate": { + "version": "1.0.2", + "from": "util-deprecate@1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "dev": true + }, + "uuid": { + "version": "3.0.1", + "from": "uuid@3.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz", + "dev": true, + "optional": true + }, + "verror": { + "version": "1.3.6", + "from": "verror@1.3.6", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz", + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.2", + "from": "wide-align@1.1.2", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", + "dev": true, + "optional": true + }, + "wrappy": { + "version": "1.0.2", + "from": "wrappy@1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "dev": true + } + } + }, "fstream": { "version": "1.0.11", "from": "fstream@>=1.0.2 <2.0.0", @@ -2661,6 +4076,22 @@ } } }, + "ftp": { + "version": "0.3.10", + "from": "ftp@>=0.3.10 <0.4.0", + "resolved": "https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz", + "dev": true, + "optional": true, + "dependencies": { + "readable-stream": { + "version": "1.1.14", + "from": "readable-stream@>=1.1.0 <1.2.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "dev": true, + "optional": true + } + } + }, "function-bind": { "version": "1.1.1", "from": "function-bind@>=1.0.2 <2.0.0", @@ -2677,6 +4108,20 @@ "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.2.tgz", "dev": true }, + "generate-function": { + "version": "2.0.0", + "from": "generate-function@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", + "dev": true, + "optional": true + }, + "generate-object-property": { + "version": "1.2.0", + "from": "generate-object-property@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "dev": true, + "optional": true + }, "generic-pool": { "version": "2.4.2", "from": "generic-pool@2.4.2", @@ -2705,6 +4150,50 @@ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", "dev": true }, + "get-uri": { + "version": "2.0.1", + "from": "get-uri@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-2.0.1.tgz", + "dev": true, + "optional": true, + "dependencies": { + "debug": { + "version": "2.6.9", + "from": "debug@2", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "dev": true, + "optional": true + }, + "isarray": { + "version": "1.0.0", + "from": "isarray@~1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "from": "process-nextick-args@~2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "dev": true, + "optional": true + }, + "readable-stream": { + "version": "2.3.4", + "from": "readable-stream@2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", + "dev": true, + "optional": true + }, + "string_decoder": { + "version": "1.0.3", + "from": "string_decoder@~1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "dev": true, + "optional": true + } + } + }, "get-value": { "version": "2.0.6", "from": "get-value@>=2.0.6 <3.0.0", @@ -3268,12 +4757,32 @@ "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", "dev": true }, + "has-binary2": { + "version": "1.0.2", + "from": "has-binary2@>=1.0.2 <1.1.0", + "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.2.tgz", + "dev": true, + "dependencies": { + "isarray": { + "version": "2.0.1", + "from": "isarray@2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "dev": true + } + } + }, "has-color": { "version": "0.1.7", "from": "has-color@>=0.1.0 <0.2.0", "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", "dev": true }, + "has-cors": { + "version": "1.1.0", + "from": "has-cors@1.1.0", + "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "dev": true + }, "has-flag": { "version": "1.0.0", "from": "has-flag@>=1.0.0 <2.0.0", @@ -3342,6 +4851,13 @@ "from": "hide-powered-by@1.0.0", "resolved": "https://registry.npmjs.org/hide-powered-by/-/hide-powered-by-1.0.0.tgz" }, + "hipchat-notifier": { + "version": "1.1.0", + "from": "hipchat-notifier@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/hipchat-notifier/-/hipchat-notifier-1.1.0.tgz", + "dev": true, + "optional": true + }, "hmac-drbg": { "version": "1.0.1", "from": "hmac-drbg@>=1.0.0 <2.0.0", @@ -3417,6 +4933,12 @@ "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", "dev": true }, + "htmlescape": { + "version": "1.1.1", + "from": "htmlescape@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz", + "dev": true + }, "htmlparser2": { "version": "3.9.2", "from": "htmlparser2@>=3.9.0 <4.0.0", @@ -3472,6 +4994,20 @@ "from": "http-proxy@>=1.8.1 <2.0.0", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.16.2.tgz" }, + "http-proxy-agent": { + "version": "1.0.0", + "from": "http-proxy-agent@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-1.0.0.tgz", + "dev": true, + "dependencies": { + "debug": { + "version": "2.6.9", + "from": "debug@2", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "dev": true + } + } + }, "http-proxy-middleware": { "version": "0.17.4", "from": "http-proxy-middleware@>=0.17.4 <0.18.0", @@ -3549,12 +5085,46 @@ "from": "http-signature@>=1.2.0 <1.3.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz" }, + "httpntlm": { + "version": "1.6.1", + "from": "httpntlm@1.6.1", + "resolved": "https://registry.npmjs.org/httpntlm/-/httpntlm-1.6.1.tgz", + "dev": true, + "dependencies": { + "underscore": { + "version": "1.7.0", + "from": "underscore@>=1.7.0 <1.8.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", + "dev": true + } + } + }, + "httpreq": { + "version": "0.4.24", + "from": "httpreq@>=0.4.22", + "resolved": "https://registry.npmjs.org/httpreq/-/httpreq-0.4.24.tgz", + "dev": true + }, "https-browserify": { "version": "1.0.0", "from": "https-browserify@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", "dev": true }, + "https-proxy-agent": { + "version": "1.0.0", + "from": "https-proxy-agent@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-1.0.0.tgz", + "dev": true, + "dependencies": { + "debug": { + "version": "2.6.9", + "from": "debug@2", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "dev": true + } + } + }, "i": { "version": "0.3.6", "from": "i@>=0.3.0 <0.4.0", @@ -3645,6 +5215,46 @@ "from": "ini@>=1.3.0 <1.4.0", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz" }, + "inline-source-map": { + "version": "0.6.2", + "from": "inline-source-map@>=0.6.0 <0.7.0", + "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz", + "dev": true, + "dependencies": { + "source-map": { + "version": "0.5.7", + "from": "source-map@>=0.5.3 <0.6.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "dev": true + } + } + }, + "insert-module-globals": { + "version": "7.0.1", + "from": "insert-module-globals@>=7.0.0 <8.0.0", + "resolved": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-7.0.1.tgz", + "dev": true, + "dependencies": { + "combine-source-map": { + "version": "0.7.2", + "from": "combine-source-map@>=0.7.1 <0.8.0", + "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.7.2.tgz", + "dev": true + }, + "convert-source-map": { + "version": "1.1.3", + "from": "convert-source-map@~1.1.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "from": "source-map@>=0.5.3 <0.6.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "dev": true + } + } + }, "internal-ip": { "version": "1.2.0", "from": "internal-ip@1.2.0", @@ -3826,6 +5436,13 @@ "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", "dev": true }, + "is-my-json-valid": { + "version": "2.17.1", + "from": "is-my-json-valid@>=2.12.4 <3.0.0", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.17.1.tgz", + "dev": true, + "optional": true + }, "is-npm": { "version": "1.0.0", "from": "is-npm@>=1.0.0 <2.0.0", @@ -3891,6 +5508,13 @@ "from": "is-promise@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-1.0.1.tgz" }, + "is-property": { + "version": "1.0.2", + "from": "is-property@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "dev": true, + "optional": true + }, "is-redirect": { "version": "1.0.0", "from": "is-redirect@>=1.0.0 <2.0.0", @@ -4063,6 +5687,25 @@ "from": "jsonify@>=0.0.0 <0.1.0", "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz" }, + "jsonparse": { + "version": "1.3.1", + "from": "jsonparse@>=1.2.0 <2.0.0", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "dev": true + }, + "jsonpointer": { + "version": "4.0.1", + "from": "jsonpointer@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", + "dev": true, + "optional": true + }, + "JSONStream": { + "version": "1.3.2", + "from": "JSONStream@>=1.0.3 <2.0.0", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.2.tgz", + "dev": true + }, "jsonwebtoken": { "version": "8.1.1", "from": "jsonwebtoken@>=8.0.1 <9.0.0", @@ -4117,6 +5760,204 @@ "from": "kareem@1.5.0", "resolved": "https://registry.npmjs.org/kareem/-/kareem-1.5.0.tgz" }, + "karma": { + "version": "2.0.0", + "from": "karma@latest", + "resolved": "https://registry.npmjs.org/karma/-/karma-2.0.0.tgz", + "dev": true, + "dependencies": { + "anymatch": { + "version": "1.3.2", + "from": "anymatch@>=1.3.0 <2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", + "dev": true + }, + "arr-diff": { + "version": "2.0.0", + "from": "arr-diff@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "dev": true + }, + "array-unique": { + "version": "0.2.1", + "from": "array-unique@>=0.2.1 <0.3.0", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "dev": true + }, + "bluebird": { + "version": "3.5.1", + "from": "bluebird@>=3.3.0 <4.0.0", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", + "dev": true + }, + "braces": { + "version": "1.8.5", + "from": "braces@>=1.8.2 <2.0.0", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "dev": true + }, + "chokidar": { + "version": "1.7.0", + "from": "chokidar@>=1.4.1 <2.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "dev": true + }, + "colors": { + "version": "1.1.2", + "from": "colors@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "dev": true + }, + "expand-brackets": { + "version": "0.1.5", + "from": "expand-brackets@>=0.1.4 <0.2.0", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "dev": true + }, + "extglob": { + "version": "0.3.2", + "from": "extglob@>=0.3.1 <0.4.0", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "dev": true + }, + "glob": { + "version": "7.1.2", + "from": "glob@>=7.1.1 <8.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "dev": true + }, + "glob-parent": { + "version": "2.0.0", + "from": "glob-parent@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "dev": true + }, + "is-extglob": { + "version": "1.0.0", + "from": "is-extglob@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "from": "is-glob@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "from": "isarray@~1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "dev": true + }, + "isbinaryfile": { + "version": "3.0.2", + "from": "isbinaryfile@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.2.tgz", + "dev": true + }, + "micromatch": { + "version": "2.3.11", + "from": "micromatch@>=2.1.5 <3.0.0", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "from": "minimatch@>=3.0.2 <4.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.0", + "from": "process-nextick-args@~2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "dev": true + }, + "range-parser": { + "version": "1.2.0", + "from": "range-parser@>=1.2.0 <2.0.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "dev": true + }, + "readable-stream": { + "version": "2.3.4", + "from": "readable-stream@^2.0.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", + "dev": true + }, + "readdirp": { + "version": "2.1.0", + "from": "readdirp@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", + "dev": true + }, + "rimraf": { + "version": "2.6.2", + "from": "rimraf@>=2.6.0 <3.0.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "from": "source-map@>=0.6.1 <0.7.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "dev": true + }, + "string_decoder": { + "version": "1.0.3", + "from": "string_decoder@~1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "dev": true + } + } + }, + "karma-chai-sinon": { + "version": "0.1.5", + "from": "karma-chai-sinon@latest", + "resolved": "https://registry.npmjs.org/karma-chai-sinon/-/karma-chai-sinon-0.1.5.tgz", + "dev": true + }, + "karma-chrome-launcher": { + "version": "2.2.0", + "from": "karma-chrome-launcher@latest", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz", + "dev": true, + "dependencies": { + "which": { + "version": "1.3.0", + "from": "which@>=1.2.1 <2.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", + "dev": true + } + } + }, + "karma-mocha": { + "version": "1.3.0", + "from": "karma-mocha@latest", + "resolved": "https://registry.npmjs.org/karma-mocha/-/karma-mocha-1.3.0.tgz", + "dev": true, + "dependencies": { + "minimist": { + "version": "1.2.0", + "from": "minimist@1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "dev": true + } + } + }, + "karma-requirejs": { + "version": "1.1.0", + "from": "karma-requirejs@latest", + "resolved": "https://registry.npmjs.org/karma-requirejs/-/karma-requirejs-1.1.0.tgz", + "dev": true + }, + "karma-tap-reporter": { + "version": "0.0.6", + "from": "karma-tap-reporter@latest", + "resolved": "https://registry.npmjs.org/karma-tap-reporter/-/karma-tap-reporter-0.0.6.tgz", + "dev": true + }, "keygrip": { "version": "1.0.2", "from": "keygrip@>=1.0.2 <1.1.0", @@ -4134,6 +5975,12 @@ "from": "kind-of@>=3.0.2 <4.0.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz" }, + "labeled-stream-splicer": { + "version": "2.0.0", + "from": "labeled-stream-splicer@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.0.tgz", + "dev": true + }, "latest-version": { "version": "3.1.0", "from": "latest-version@>=3.0.0 <4.0.0", @@ -4395,6 +6242,19 @@ } } }, + "levn": { + "version": "0.3.0", + "from": "levn@>=0.3.0 <0.4.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "dev": true, + "optional": true + }, + "lexical-scope": { + "version": "1.2.0", + "from": "lexical-scope@>=1.2.0 <2.0.0", + "resolved": "https://registry.npmjs.org/lexical-scope/-/lexical-scope-1.2.0.tgz", + "dev": true + }, "libbase64": { "version": "0.1.0", "from": "libbase64@0.1.0", @@ -4564,6 +6424,12 @@ "from": "lodash.keys@>=4.2.0 <5.0.0", "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-4.2.0.tgz" }, + "lodash.memoize": { + "version": "3.0.4", + "from": "lodash.memoize@>=3.0.3 <3.1.0", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", + "dev": true + }, "lodash.mergewith": { "version": "4.6.0", "from": "lodash.mergewith@>=4.6.0 <5.0.0", @@ -4610,6 +6476,112 @@ "from": "lodash.values@>=4.3.0 <5.0.0", "resolved": "https://registry.npmjs.org/lodash.values/-/lodash.values-4.3.0.tgz" }, + "log4js": { + "version": "2.5.3", + "from": "log4js@>=2.3.9 <3.0.0", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-2.5.3.tgz", + "dev": true, + "dependencies": { + "addressparser": { + "version": "1.0.1", + "from": "addressparser@1.0.1", + "resolved": "https://registry.npmjs.org/addressparser/-/addressparser-1.0.1.tgz", + "dev": true, + "optional": true + }, + "buildmail": { + "version": "4.0.1", + "from": "buildmail@4.0.1", + "resolved": "https://registry.npmjs.org/buildmail/-/buildmail-4.0.1.tgz", + "dev": true, + "optional": true + }, + "debug": { + "version": "3.1.0", + "from": "debug@>=3.1.0 <4.0.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "dev": true + }, + "iconv-lite": { + "version": "0.4.15", + "from": "iconv-lite@0.4.15", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", + "dev": true + }, + "libmime": { + "version": "3.0.0", + "from": "libmime@3.0.0", + "resolved": "https://registry.npmjs.org/libmime/-/libmime-3.0.0.tgz", + "dev": true + }, + "mailcomposer": { + "version": "4.0.1", + "from": "mailcomposer@4.0.1", + "resolved": "https://registry.npmjs.org/mailcomposer/-/mailcomposer-4.0.1.tgz", + "dev": true, + "optional": true + }, + "nodemailer": { + "version": "2.7.2", + "from": "nodemailer@>=2.5.0 <3.0.0", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-2.7.2.tgz", + "dev": true, + "optional": true + }, + "nodemailer-direct-transport": { + "version": "3.3.2", + "from": "nodemailer-direct-transport@3.3.2", + "resolved": "https://registry.npmjs.org/nodemailer-direct-transport/-/nodemailer-direct-transport-3.3.2.tgz", + "dev": true, + "optional": true + }, + "nodemailer-fetch": { + "version": "1.6.0", + "from": "nodemailer-fetch@1.6.0", + "resolved": "https://registry.npmjs.org/nodemailer-fetch/-/nodemailer-fetch-1.6.0.tgz", + "dev": true + }, + "nodemailer-shared": { + "version": "1.1.0", + "from": "nodemailer-shared@1.1.0", + "resolved": "https://registry.npmjs.org/nodemailer-shared/-/nodemailer-shared-1.1.0.tgz", + "dev": true + }, + "nodemailer-smtp-pool": { + "version": "2.8.2", + "from": "nodemailer-smtp-pool@2.8.2", + "resolved": "https://registry.npmjs.org/nodemailer-smtp-pool/-/nodemailer-smtp-pool-2.8.2.tgz", + "dev": true, + "optional": true + }, + "nodemailer-smtp-transport": { + "version": "2.7.2", + "from": "nodemailer-smtp-transport@2.7.2", + "resolved": "https://registry.npmjs.org/nodemailer-smtp-transport/-/nodemailer-smtp-transport-2.7.2.tgz", + "dev": true, + "optional": true + }, + "nodemailer-wellknown": { + "version": "0.1.10", + "from": "nodemailer-wellknown@0.1.10", + "resolved": "https://registry.npmjs.org/nodemailer-wellknown/-/nodemailer-wellknown-0.1.10.tgz", + "dev": true + }, + "smtp-connection": { + "version": "2.12.0", + "from": "smtp-connection@2.12.0", + "resolved": "https://registry.npmjs.org/smtp-connection/-/smtp-connection-2.12.0.tgz", + "dev": true + }, + "socks": { + "version": "1.1.9", + "from": "socks@1.1.9", + "resolved": "https://registry.npmjs.org/socks/-/socks-1.1.9.tgz", + "dev": true, + "optional": true + } + } + }, "logger-sharelatex": { "version": "1.5.7", "from": "git+https://github.com/sharelatex/logger-sharelatex.git#master", @@ -4841,6 +6813,146 @@ } } }, + "loggly": { + "version": "1.1.1", + "from": "loggly@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/loggly/-/loggly-1.1.1.tgz", + "dev": true, + "optional": true, + "dependencies": { + "assert-plus": { + "version": "0.2.0", + "from": "assert-plus@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "dev": true, + "optional": true + }, + "aws-sign2": { + "version": "0.6.0", + "from": "aws-sign2@>=0.6.0 <0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "dev": true, + "optional": true + }, + "bl": { + "version": "1.1.2", + "from": "bl@>=1.1.2 <1.2.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.1.2.tgz", + "dev": true, + "optional": true + }, + "boom": { + "version": "2.10.1", + "from": "boom@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "dev": true + }, + "caseless": { + "version": "0.11.0", + "from": "caseless@>=0.11.0 <0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", + "dev": true, + "optional": true + }, + "commander": { + "version": "2.14.1", + "from": "commander@>=2.9.0 <3.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.14.1.tgz", + "dev": true, + "optional": true + }, + "cryptiles": { + "version": "2.0.5", + "from": "cryptiles@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "dev": true, + "optional": true + }, + "form-data": { + "version": "2.0.0", + "from": "form-data@>=2.0.0 <2.1.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.0.0.tgz", + "dev": true, + "optional": true + }, + "har-validator": { + "version": "2.0.6", + "from": "har-validator@>=2.0.6 <2.1.0", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", + "dev": true, + "optional": true + }, + "hawk": { + "version": "3.1.3", + "from": "hawk@>=3.1.3 <3.2.0", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "dev": true, + "optional": true + }, + "hoek": { + "version": "2.16.3", + "from": "hoek@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "dev": true + }, + "http-signature": { + "version": "1.1.1", + "from": "http-signature@>=1.1.0 <1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "dev": true, + "optional": true + }, + "isarray": { + "version": "1.0.0", + "from": "isarray@~1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "dev": true, + "optional": true + }, + "node-uuid": { + "version": "1.4.8", + "from": "node-uuid@>=1.4.7 <1.5.0", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "dev": true, + "optional": true + }, + "qs": { + "version": "6.2.3", + "from": "qs@>=6.2.0 <6.3.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.2.3.tgz", + "dev": true, + "optional": true + }, + "readable-stream": { + "version": "2.0.6", + "from": "readable-stream@>=2.0.5 <2.1.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "dev": true, + "optional": true + }, + "request": { + "version": "2.75.0", + "from": "request@>=2.75.0 <2.76.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.75.0.tgz", + "dev": true, + "optional": true + }, + "sntp": { + "version": "1.0.9", + "from": "sntp@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "dev": true, + "optional": true + }, + "tunnel-agent": { + "version": "0.4.3", + "from": "tunnel-agent@>=0.4.1 <0.5.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", + "dev": true, + "optional": true + } + } + }, "loglevel": { "version": "1.6.1", "from": "loglevel@>=1.4.1 <2.0.0", @@ -4902,6 +7014,57 @@ "from": "mailcomposer@3.3.2", "resolved": "https://registry.npmjs.org/mailcomposer/-/mailcomposer-3.3.2.tgz" }, + "mailgun-js": { + "version": "0.7.15", + "from": "mailgun-js@>=0.7.0 <0.8.0", + "resolved": "https://registry.npmjs.org/mailgun-js/-/mailgun-js-0.7.15.tgz", + "dev": true, + "optional": true, + "dependencies": { + "async": { + "version": "2.1.5", + "from": "async@>=2.1.2 <2.2.0", + "resolved": "https://registry.npmjs.org/async/-/async-2.1.5.tgz", + "dev": true, + "optional": true + }, + "debug": { + "version": "2.2.0", + "from": "debug@>=2.2.0 <2.3.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "dev": true, + "optional": true + }, + "form-data": { + "version": "2.1.4", + "from": "form-data@>=2.1.1 <2.2.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "dev": true, + "optional": true + }, + "inflection": { + "version": "1.10.0", + "from": "inflection@>=1.10.0 <1.11.0", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.10.0.tgz", + "dev": true, + "optional": true + }, + "ms": { + "version": "0.7.1", + "from": "ms@0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "dev": true, + "optional": true + }, + "q": { + "version": "1.4.1", + "from": "q@>=1.4.0 <1.5.0", + "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", + "dev": true, + "optional": true + } + } + }, "make-dir": { "version": "1.1.0", "from": "make-dir@>=1.0.0 <2.0.0", @@ -5193,7 +7356,7 @@ }, "mocha": { "version": "1.17.1", - "from": "mocha@1.17.1", + "from": "https://registry.npmjs.org/mocha/-/mocha-1.17.1.tgz", "resolved": "https://registry.npmjs.org/mocha/-/mocha-1.17.1.tgz", "dependencies": { "commander": { @@ -5235,6 +7398,38 @@ } } }, + "module-deps": { + "version": "4.1.1", + "from": "module-deps@>=4.0.8 <5.0.0", + "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-4.1.1.tgz", + "dev": true, + "dependencies": { + "isarray": { + "version": "1.0.0", + "from": "isarray@~1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.0", + "from": "process-nextick-args@~2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "dev": true + }, + "readable-stream": { + "version": "2.3.4", + "from": "readable-stream@^2.0.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", + "dev": true + }, + "string_decoder": { + "version": "1.0.3", + "from": "string_decoder@~1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "dev": true + } + } + }, "moment": { "version": "2.20.1", "from": "moment@>=2.10.6 <3.0.0", @@ -5497,6 +7692,13 @@ "from": "negotiator@0.5.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.5.3.tgz" }, + "netmask": { + "version": "1.0.6", + "from": "netmask@>=1.0.4 <1.1.0", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-1.0.6.tgz", + "dev": true, + "optional": true + }, "nise": { "version": "1.2.2", "from": "nise@>=1.2.0 <2.0.0", @@ -5739,6 +7941,12 @@ "resolved": "https://registry.npmjs.org/nssocket/-/nssocket-0.5.3.tgz", "dev": true }, + "null-check": { + "version": "1.0.0", + "from": "null-check@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz", + "dev": true + }, "num2fraction": { "version": "1.2.2", "from": "num2fraction@>=1.2.2 <2.0.0", @@ -5765,6 +7973,12 @@ "from": "object-assign@>=4.1.0 <5.0.0", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" }, + "object-component": { + "version": "0.0.3", + "from": "object-component@0.0.3", + "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", + "dev": true + }, "object-copy": { "version": "0.1.0", "from": "object-copy@>=0.1.0 <0.2.0", @@ -6014,6 +8228,22 @@ "from": "optimist@0.6.1", "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz" }, + "optionator": { + "version": "0.8.2", + "from": "optionator@>=0.8.1 <0.9.0", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "dev": true, + "optional": true, + "dependencies": { + "wordwrap": { + "version": "1.0.0", + "from": "wordwrap@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "dev": true, + "optional": true + } + } + }, "original": { "version": "1.0.0", "from": "original@>=0.0.5", @@ -6085,6 +8315,45 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", "dev": true }, + "pac-proxy-agent": { + "version": "1.1.0", + "from": "pac-proxy-agent@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-1.1.0.tgz", + "dev": true, + "optional": true, + "dependencies": { + "debug": { + "version": "2.6.9", + "from": "debug@2", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "dev": true, + "optional": true + } + } + }, + "pac-resolver": { + "version": "2.0.0", + "from": "pac-resolver@>=2.0.0 <2.1.0", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-2.0.0.tgz", + "dev": true, + "optional": true, + "dependencies": { + "co": { + "version": "3.0.6", + "from": "co@>=3.0.6 <3.1.0", + "resolved": "https://registry.npmjs.org/co/-/co-3.0.6.tgz", + "dev": true, + "optional": true + }, + "ip": { + "version": "1.0.1", + "from": "ip@1.0.1", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.0.1.tgz", + "dev": true, + "optional": true + } + } + }, "package-json": { "version": "4.0.1", "from": "package-json@>=4.0.0 <5.0.0", @@ -6097,6 +8366,12 @@ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", "dev": true }, + "parents": { + "version": "1.0.1", + "from": "parents@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", + "dev": true + }, "parse-asn1": { "version": "5.1.0", "from": "parse-asn1@>=5.0.0 <6.0.0", @@ -6134,6 +8409,18 @@ "from": "parse-mongo-url@>=1.1.0 <2.0.0", "resolved": "https://registry.npmjs.org/parse-mongo-url/-/parse-mongo-url-1.1.1.tgz" }, + "parseqs": { + "version": "0.0.5", + "from": "parseqs@0.0.5", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", + "dev": true + }, + "parseuri": { + "version": "0.0.5", + "from": "parseuri@0.0.5", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", + "dev": true + }, "parseurl": { "version": "1.3.2", "from": "parseurl@>=1.3.0 <1.4.0", @@ -6244,6 +8531,28 @@ "from": "path-parse@>=1.0.5 <2.0.0", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz" }, + "path-platform": { + "version": "0.11.15", + "from": "path-platform@>=0.11.15 <0.12.0", + "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz", + "dev": true + }, + "path-proxy": { + "version": "1.0.0", + "from": "path-proxy@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/path-proxy/-/path-proxy-1.0.0.tgz", + "dev": true, + "optional": true, + "dependencies": { + "inflection": { + "version": "1.3.8", + "from": "inflection@>=1.3.0 <1.4.0", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.3.8.tgz", + "dev": true, + "optional": true + } + } + }, "path-to-regexp": { "version": "0.1.6", "from": "path-to-regexp@0.1.6", @@ -6398,6 +8707,12 @@ "from": "precond@>=0.2.0 <0.3.0", "resolved": "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz" }, + "prelude-ls": { + "version": "1.1.2", + "from": "prelude-ls@>=1.1.2 <1.2.0", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "dev": true + }, "prepend-http": { "version": "1.0.4", "from": "prepend-http@>=1.0.1 <2.0.0", @@ -6443,6 +8758,29 @@ "from": "proxy-addr@>=1.0.8 <1.1.0", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.0.10.tgz" }, + "proxy-agent": { + "version": "2.0.0", + "from": "proxy-agent@>=2.0.0 <2.1.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-2.0.0.tgz", + "dev": true, + "optional": true, + "dependencies": { + "debug": { + "version": "2.6.9", + "from": "debug@2", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "dev": true, + "optional": true + }, + "lru-cache": { + "version": "2.6.5", + "from": "lru-cache@>=2.6.5 <2.7.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.6.5.tgz", + "dev": true, + "optional": true + } + } + }, "prr": { "version": "1.0.1", "from": "prr@>=1.0.1 <1.1.0", @@ -6614,6 +8952,12 @@ "from": "q@>=1.1.0 <1.2.0", "resolved": "https://registry.npmjs.org/q/-/q-1.1.2.tgz" }, + "qjobs": { + "version": "1.1.5", + "from": "qjobs@>=1.1.4 <2.0.0", + "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.1.5.tgz", + "dev": true + }, "qs": { "version": "6.5.1", "from": "qs@>=6.5.1 <6.6.0", @@ -6719,6 +9063,38 @@ "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", "dev": true }, + "read-only-stream": { + "version": "2.0.0", + "from": "read-only-stream@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz", + "dev": true, + "dependencies": { + "isarray": { + "version": "1.0.0", + "from": "isarray@~1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.0", + "from": "process-nextick-args@~2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "dev": true + }, + "readable-stream": { + "version": "2.3.4", + "from": "readable-stream@^2.0.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", + "dev": true + }, + "string_decoder": { + "version": "1.0.3", + "from": "string_decoder@~1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "dev": true + } + } + }, "read-pkg": { "version": "2.0.0", "from": "read-pkg@>=2.0.0 <3.0.0", @@ -6933,7 +9309,7 @@ }, "requestretry": { "version": "1.13.0", - "from": "requestretry@latest", + "from": "requestretry@>=1.2.2 <2.0.0", "resolved": "https://registry.npmjs.org/requestretry/-/requestretry-1.13.0.tgz" }, "requests": { @@ -6972,7 +9348,7 @@ }, "requirejs": { "version": "2.1.22", - "from": "requirejs@>=2.1.0 <2.2.0", + "from": "https://registry.npmjs.org/requirejs/-/requirejs-2.1.22.tgz", "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.1.22.tgz", "dev": true }, @@ -7370,6 +9746,20 @@ "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.10.tgz", "dev": true }, + "shasum": { + "version": "1.0.2", + "from": "shasum@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz", + "dev": true, + "dependencies": { + "json-stable-stringify": { + "version": "0.0.1", + "from": "json-stable-stringify@>=0.0.0 <0.1.0", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz", + "dev": true + } + } + }, "shebang-command": { "version": "1.2.0", "from": "shebang-command@>=1.2.0 <2.0.0", @@ -7382,6 +9772,12 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", "dev": true }, + "shell-quote": { + "version": "1.6.1", + "from": "shell-quote@>=1.6.1 <2.0.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz", + "dev": true + }, "shimmer": { "version": "1.1.0", "from": "shimmer@1.1.0", @@ -7399,15 +9795,28 @@ }, "sinon": { "version": "1.17.7", - "from": "sinon@>=1.17.0 <2.0.0", + "from": "https://registry.npmjs.org/sinon/-/sinon-1.17.7.tgz", "resolved": "https://registry.npmjs.org/sinon/-/sinon-1.17.7.tgz", "dev": true }, + "sinon-chai": { + "version": "2.14.0", + "from": "sinon-chai@latest", + "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-2.14.0.tgz", + "dev": true + }, "sixpack-client": { "version": "1.0.0", "from": "sixpack-client@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/sixpack-client/-/sixpack-client-1.0.0.tgz" }, + "slack-node": { + "version": "0.2.0", + "from": "slack-node@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/slack-node/-/slack-node-0.2.0.tgz", + "dev": true, + "optional": true + }, "slash": { "version": "1.0.0", "from": "slash@>=1.0.0 <2.0.0", @@ -7419,6 +9828,12 @@ "from": "sliced@1.0.1", "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz" }, + "smart-buffer": { + "version": "1.1.15", + "from": "smart-buffer@>=1.0.13 <2.0.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-1.1.15.tgz", + "dev": true + }, "smtp-connection": { "version": "2.0.1", "from": "smtp-connection@2.0.1", @@ -7512,6 +9927,60 @@ "from": "sntp@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz" }, + "socket.io": { + "version": "2.0.4", + "from": "socket.io@2.0.4", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.0.4.tgz", + "dev": true, + "dependencies": { + "debug": { + "version": "2.6.9", + "from": "debug@~2.6.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "dev": true + } + } + }, + "socket.io-adapter": { + "version": "1.1.1", + "from": "socket.io-adapter@>=1.1.0 <1.2.0", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz", + "dev": true + }, + "socket.io-client": { + "version": "2.0.4", + "from": "socket.io-client@2.0.4", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.0.4.tgz", + "dev": true, + "dependencies": { + "debug": { + "version": "2.6.9", + "from": "debug@~2.6.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "dev": true + } + } + }, + "socket.io-parser": { + "version": "3.1.2", + "from": "socket.io-parser@>=3.1.1 <3.2.0", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.1.2.tgz", + "dev": true, + "dependencies": { + "debug": { + "version": "2.6.9", + "from": "debug@~2.6.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "dev": true + }, + "isarray": { + "version": "2.0.1", + "from": "isarray@2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "dev": true + } + } + }, "sockjs": { "version": "0.3.19", "from": "sockjs@0.3.19", @@ -7538,6 +10007,18 @@ } } }, + "socks": { + "version": "1.1.10", + "from": "socks@>=1.1.5 <1.2.0", + "resolved": "https://registry.npmjs.org/socks/-/socks-1.1.10.tgz", + "dev": true + }, + "socks-proxy-agent": { + "version": "2.1.1", + "from": "socks-proxy-agent@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-2.1.1.tgz", + "dev": true + }, "source-list-map": { "version": "2.0.0", "from": "source-list-map@>=2.0.0 <3.0.0", @@ -7777,6 +10258,38 @@ "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", "dev": true }, + "stream-combiner2": { + "version": "1.1.1", + "from": "stream-combiner2@>=1.1.1 <2.0.0", + "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", + "dev": true, + "dependencies": { + "isarray": { + "version": "1.0.0", + "from": "isarray@~1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.0", + "from": "process-nextick-args@~2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "dev": true + }, + "readable-stream": { + "version": "2.3.4", + "from": "readable-stream@^2.0.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", + "dev": true + }, + "string_decoder": { + "version": "1.0.3", + "from": "string_decoder@~1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "dev": true + } + } + }, "stream-http": { "version": "2.8.0", "from": "stream-http@>=2.7.2 <3.0.0", @@ -7803,6 +10316,76 @@ } } }, + "stream-splicer": { + "version": "2.0.0", + "from": "stream-splicer@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-2.0.0.tgz", + "dev": true, + "dependencies": { + "isarray": { + "version": "1.0.0", + "from": "isarray@~1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.0", + "from": "process-nextick-args@~2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "dev": true + }, + "readable-stream": { + "version": "2.3.4", + "from": "readable-stream@^2.0.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", + "dev": true + }, + "string_decoder": { + "version": "1.0.3", + "from": "string_decoder@~1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "dev": true + } + } + }, + "streamroller": { + "version": "0.7.0", + "from": "streamroller@>=0.7.0 <0.8.0", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-0.7.0.tgz", + "dev": true, + "dependencies": { + "debug": { + "version": "3.1.0", + "from": "debug@>=3.1.0 <4.0.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "from": "isarray@~1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.0", + "from": "process-nextick-args@~2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "dev": true + }, + "readable-stream": { + "version": "2.3.4", + "from": "readable-stream@^2.3.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", + "dev": true + }, + "string_decoder": { + "version": "1.0.3", + "from": "string_decoder@~1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "dev": true + } + } + }, "streamsearch": { "version": "0.1.2", "from": "streamsearch@0.1.2", @@ -7851,12 +10434,32 @@ "from": "strip-json-comments@>=2.0.1 <2.1.0", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz" }, + "subarg": { + "version": "1.0.0", + "from": "subarg@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", + "dev": true, + "dependencies": { + "minimist": { + "version": "1.2.0", + "from": "minimist@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "dev": true + } + } + }, "supports-color": { "version": "3.2.3", "from": "supports-color@>=3.2.3 <4.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "dev": true }, + "syntax-error": { + "version": "1.4.0", + "from": "syntax-error@>=1.1.1 <2.0.0", + "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz", + "dev": true + }, "tapable": { "version": "0.2.8", "from": "tapable@>=0.2.7 <0.3.0", @@ -7947,6 +10550,45 @@ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "dev": true }, + "through2": { + "version": "2.0.3", + "from": "through2@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "dev": true, + "dependencies": { + "isarray": { + "version": "1.0.0", + "from": "isarray@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.0", + "from": "process-nextick-args@>=2.0.0 <2.1.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "dev": true + }, + "readable-stream": { + "version": "2.3.4", + "from": "readable-stream@^2.1.5", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", + "dev": true + }, + "string_decoder": { + "version": "1.0.3", + "from": "string_decoder@~1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "dev": true + } + } + }, + "thunkify": { + "version": "2.1.2", + "from": "thunkify@>=2.1.1 <2.2.0", + "resolved": "https://registry.npmjs.org/thunkify/-/thunkify-2.1.2.tgz", + "dev": true, + "optional": true + }, "thunky": { "version": "0.1.0", "from": "thunky@>=0.1.0 <0.2.0", @@ -7966,7 +10608,7 @@ }, "timekeeper": { "version": "2.0.0", - "from": "timekeeper@latest", + "from": "timekeeper@", "resolved": "https://registry.npmjs.org/timekeeper/-/timekeeper-2.0.0.tgz" }, "timers-browserify": { @@ -8053,6 +10695,18 @@ } } }, + "tmp": { + "version": "0.0.33", + "from": "tmp@0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "dev": true + }, + "to-array": { + "version": "0.1.4", + "from": "to-array@0.1.4", + "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", + "dev": true + }, "to-arraybuffer": { "version": "1.0.1", "from": "to-arraybuffer@>=1.0.0 <2.0.0", @@ -8230,6 +10884,12 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "optional": true }, + "type-check": { + "version": "0.3.2", + "from": "type-check@>=0.3.2 <0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "dev": true + }, "type-detect": { "version": "1.0.0", "from": "type-detect@>=1.0.0 <2.0.0", @@ -8241,6 +10901,12 @@ "from": "type-is@>=1.6.15 <1.7.0", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz" }, + "typedarray": { + "version": "0.0.6", + "from": "typedarray@>=0.0.6 <0.0.7", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "dev": true + }, "uglify-js": { "version": "2.4.24", "from": "uglify-js@>=2.4.0 <2.5.0", @@ -8299,6 +10965,18 @@ "from": "uid2@>=0.0.0 <0.1.0", "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.3.tgz" }, + "ultron": { + "version": "1.1.1", + "from": "ultron@>=1.1.0 <1.2.0", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", + "dev": true + }, + "umd": { + "version": "3.0.1", + "from": "umd@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.1.tgz", + "dev": true + }, "undefsafe": { "version": "2.0.1", "from": "undefsafe@>=2.0.1 <3.0.0", @@ -8523,6 +11201,20 @@ } } }, + "useragent": { + "version": "2.3.0", + "from": "useragent@>=2.1.12 <3.0.0", + "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz", + "dev": true, + "dependencies": { + "lru-cache": { + "version": "4.1.1", + "from": "lru-cache@>=4.1.0 <4.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", + "dev": true + } + } + }, "util": { "version": "0.10.3", "from": "util@>=0.10.3 <1.0.0", @@ -8572,6 +11264,13 @@ "from": "uuid@>=3.0.1 <4.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz" }, + "uws": { + "version": "0.14.5", + "from": "uws@>=0.14.4 <0.15.0", + "resolved": "https://registry.npmjs.org/uws/-/uws-0.14.5.tgz", + "dev": true, + "optional": true + }, "v8-profiler": { "version": "5.7.0", "from": "v8-profiler@>=5.2.3 <6.0.0", @@ -9423,6 +12122,12 @@ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz", "dev": true }, + "ws": { + "version": "3.3.3", + "from": "ws@>=3.3.1 <3.4.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "dev": true + }, "x-xss-protection": { "version": "1.0.0", "from": "x-xss-protection@1.0.0", @@ -9488,6 +12193,12 @@ "from": "xmldom@>=0.1.0 <0.2.0", "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz" }, + "xmlhttprequest-ssl": { + "version": "1.5.5", + "from": "xmlhttprequest-ssl@>=1.5.4 <1.6.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", + "dev": true + }, "xpath": { "version": "0.0.5", "from": "xpath@0.0.5", @@ -9498,6 +12209,13 @@ "from": "xpath.js@>=0.0.3", "resolved": "https://registry.npmjs.org/xpath.js/-/xpath.js-1.1.0.tgz" }, + "xregexp": { + "version": "2.0.0", + "from": "xregexp@2.0.0", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", + "dev": true, + "optional": true + }, "xtend": { "version": "4.0.1", "from": "xtend@>=4.0.1 <5.0.0", @@ -9539,6 +12257,12 @@ "from": "yauzl@>=2.8.0 <3.0.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.9.1.tgz" }, + "yeast": { + "version": "0.1.2", + "from": "yeast@0.1.2", + "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "dev": true + }, "zip-stream": { "version": "0.3.7", "from": "zip-stream@>=0.3.0 <0.4.0", diff --git a/services/web/package.json b/services/web/package.json index 01dec84282..38c1891e0e 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -15,7 +15,7 @@ "test:acceptance:dir": "npm -q run test:acceptance:wait_for_app && npm -q run test:acceptance:run -- $@", "test:acceptance": "npm -q run test:acceptance:dir -- $@ test/acceptance/js", "test:unit": "npm -q run compile && bin/unit_test $@", - "test:frontend": "npm -q run compile && bin/frontend_test $@", + "test:frontend": "karma start --single-run", "compile": "make compile", "start": "npm -q run compile && node app.js", "nodemon": "nodemon --config nodemon.json", @@ -119,10 +119,18 @@ "grunt-postcss": "^0.8.0", "grunt-sed": "^0.1.1", "grunt-shell": "^2.1.0", + "karma": "^2.0.0", + "karma-chai-sinon": "^0.1.5", + "karma-chrome-launcher": "^2.2.0", + "karma-mocha": "^1.3.0", + "karma-requirejs": "^1.1.0", + "karma-tap-reporter": "0.0.6", "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "nodemon": "^1.14.3", + "requirejs": "^2.1.22", "sandboxed-module": "0.2.0", "sinon": "^1.17.0", + "sinon-chai": "^2.14.0", "timekeeper": "", "translations-sharelatex": "git+https://github.com/sharelatex/translations-sharelatex.git#master", "webpack": "^3.10.0", diff --git a/services/web/public/js/libs/angular-mocks.js b/services/web/public/js/libs/angular-mocks.js new file mode 100644 index 0000000000..8ee32d27a9 --- /dev/null +++ b/services/web/public/js/libs/angular-mocks.js @@ -0,0 +1,3197 @@ +'use strict'; + +/** + * @ngdoc object + * @name angular.mock + * @description + * + * Namespace from 'angular-mocks.js' which contains testing related code. + * + */ +angular.mock = {}; + +/** + * ! This is a private undocumented service ! + * + * @name $browser + * + * @description + * This service is a mock implementation of {@link ng.$browser}. It provides fake + * implementation for commonly used browser apis that are hard to test, e.g. setTimeout, xhr, + * cookies, etc. + * + * The api of this service is the same as that of the real {@link ng.$browser $browser}, except + * that there are several helper methods available which can be used in tests. + */ +angular.mock.$BrowserProvider = function() { + this.$get = function() { + return new angular.mock.$Browser(); + }; +}; + +angular.mock.$Browser = function() { + var self = this; + + this.isMock = true; + self.$$url = 'http://server/'; + self.$$lastUrl = self.$$url; // used by url polling fn + self.pollFns = []; + + // Testability API + + var outstandingRequestCount = 0; + var outstandingRequestCallbacks = []; + self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; }; + self.$$completeOutstandingRequest = function(fn) { + try { + fn(); + } finally { + outstandingRequestCount--; + if (!outstandingRequestCount) { + while (outstandingRequestCallbacks.length) { + outstandingRequestCallbacks.pop()(); + } + } + } + }; + self.notifyWhenNoOutstandingRequests = function(callback) { + if (outstandingRequestCount) { + outstandingRequestCallbacks.push(callback); + } else { + callback(); + } + }; + + // register url polling fn + + self.onUrlChange = function(listener) { + self.pollFns.push( + function() { + if (self.$$lastUrl !== self.$$url || self.$$state !== self.$$lastState) { + self.$$lastUrl = self.$$url; + self.$$lastState = self.$$state; + listener(self.$$url, self.$$state); + } + } + ); + + return listener; + }; + + self.$$applicationDestroyed = angular.noop; + self.$$checkUrlChange = angular.noop; + + self.deferredFns = []; + self.deferredNextId = 0; + + self.defer = function(fn, delay) { + // Note that we do not use `$$incOutstandingRequestCount` or `$$completeOutstandingRequest` + // in this mock implementation. + delay = delay || 0; + self.deferredFns.push({time:(self.defer.now + delay), fn:fn, id: self.deferredNextId}); + self.deferredFns.sort(function(a, b) { return a.time - b.time;}); + return self.deferredNextId++; + }; + + + /** + * @name $browser#defer.now + * + * @description + * Current milliseconds mock time. + */ + self.defer.now = 0; + + + self.defer.cancel = function(deferId) { + var fnIndex; + + angular.forEach(self.deferredFns, function(fn, index) { + if (fn.id === deferId) fnIndex = index; + }); + + if (angular.isDefined(fnIndex)) { + self.deferredFns.splice(fnIndex, 1); + return true; + } + + return false; + }; + + + /** + * @name $browser#defer.flush + * + * @description + * Flushes all pending requests and executes the defer callbacks. + * + * @param {number=} number of milliseconds to flush. See {@link #defer.now} + */ + self.defer.flush = function(delay) { + var nextTime; + + if (angular.isDefined(delay)) { + // A delay was passed so compute the next time + nextTime = self.defer.now + delay; + } else { + if (self.deferredFns.length) { + // No delay was passed so set the next time so that it clears the deferred queue + nextTime = self.deferredFns[self.deferredFns.length - 1].time; + } else { + // No delay passed, but there are no deferred tasks so flush - indicates an error! + throw new Error('No deferred tasks to be flushed'); + } + } + + while (self.deferredFns.length && self.deferredFns[0].time <= nextTime) { + // Increment the time and call the next deferred function + self.defer.now = self.deferredFns[0].time; + self.deferredFns.shift().fn(); + } + + // Ensure that the current time is correct + self.defer.now = nextTime; + }; + + self.$$baseHref = '/'; + self.baseHref = function() { + return this.$$baseHref; + }; +}; +angular.mock.$Browser.prototype = { + + /** + * @name $browser#poll + * + * @description + * run all fns in pollFns + */ + poll: function poll() { + angular.forEach(this.pollFns, function(pollFn) { + pollFn(); + }); + }, + + url: function(url, replace, state) { + if (angular.isUndefined(state)) { + state = null; + } + if (url) { + this.$$url = url; + // Native pushState serializes & copies the object; simulate it. + this.$$state = angular.copy(state); + return this; + } + + return this.$$url; + }, + + state: function() { + return this.$$state; + } +}; + + +/** + * @ngdoc provider + * @name $exceptionHandlerProvider + * + * @description + * Configures the mock implementation of {@link ng.$exceptionHandler} to rethrow or to log errors + * passed to the `$exceptionHandler`. + */ + +/** + * @ngdoc service + * @name $exceptionHandler + * + * @description + * Mock implementation of {@link ng.$exceptionHandler} that rethrows or logs errors passed + * to it. See {@link ngMock.$exceptionHandlerProvider $exceptionHandlerProvider} for configuration + * information. + * + * + * ```js + * describe('$exceptionHandlerProvider', function() { + * + * it('should capture log messages and exceptions', function() { + * + * module(function($exceptionHandlerProvider) { + * $exceptionHandlerProvider.mode('log'); + * }); + * + * inject(function($log, $exceptionHandler, $timeout) { + * $timeout(function() { $log.log(1); }); + * $timeout(function() { $log.log(2); throw 'banana peel'; }); + * $timeout(function() { $log.log(3); }); + * expect($exceptionHandler.errors).toEqual([]); + * expect($log.assertEmpty()); + * $timeout.flush(); + * expect($exceptionHandler.errors).toEqual(['banana peel']); + * expect($log.log.logs).toEqual([[1], [2], [3]]); + * }); + * }); + * }); + * ``` + */ + +angular.mock.$ExceptionHandlerProvider = function() { + var handler; + + /** + * @ngdoc method + * @name $exceptionHandlerProvider#mode + * + * @description + * Sets the logging mode. + * + * @param {string} mode Mode of operation, defaults to `rethrow`. + * + * - `log`: Sometimes it is desirable to test that an error is thrown, for this case the `log` + * mode stores an array of errors in `$exceptionHandler.errors`, to allow later assertion of + * them. See {@link ngMock.$log#assertEmpty assertEmpty()} and + * {@link ngMock.$log#reset reset()}. + * - `rethrow`: If any errors are passed to the handler in tests, it typically means that there + * is a bug in the application or test, so this mock will make these tests fail. For any + * implementations that expect exceptions to be thrown, the `rethrow` mode will also maintain + * a log of thrown errors in `$exceptionHandler.errors`. + */ + this.mode = function(mode) { + + switch (mode) { + case 'log': + case 'rethrow': + var errors = []; + handler = function(e) { + if (arguments.length === 1) { + errors.push(e); + } else { + errors.push([].slice.call(arguments, 0)); + } + if (mode === 'rethrow') { + throw e; + } + }; + handler.errors = errors; + break; + default: + throw new Error('Unknown mode \'' + mode + '\', only \'log\'/\'rethrow\' modes are allowed!'); + } + }; + + this.$get = function() { + return handler; + }; + + this.mode('rethrow'); +}; + + +/** + * @ngdoc service + * @name $log + * + * @description + * Mock implementation of {@link ng.$log} that gathers all logged messages in arrays + * (one array per logging level). These arrays are exposed as `logs` property of each of the + * level-specific log function, e.g. for level `error` the array is exposed as `$log.error.logs`. + * + */ +angular.mock.$LogProvider = function() { + var debug = true; + + function concat(array1, array2, index) { + return array1.concat(Array.prototype.slice.call(array2, index)); + } + + this.debugEnabled = function(flag) { + if (angular.isDefined(flag)) { + debug = flag; + return this; + } else { + return debug; + } + }; + + this.$get = function() { + var $log = { + log: function() { $log.log.logs.push(concat([], arguments, 0)); }, + warn: function() { $log.warn.logs.push(concat([], arguments, 0)); }, + info: function() { $log.info.logs.push(concat([], arguments, 0)); }, + error: function() { $log.error.logs.push(concat([], arguments, 0)); }, + debug: function() { + if (debug) { + $log.debug.logs.push(concat([], arguments, 0)); + } + } + }; + + /** + * @ngdoc method + * @name $log#reset + * + * @description + * Reset all of the logging arrays to empty. + */ + $log.reset = function() { + /** + * @ngdoc property + * @name $log#log.logs + * + * @description + * Array of messages logged using {@link ng.$log#log `log()`}. + * + * @example + * ```js + * $log.log('Some Log'); + * var first = $log.log.logs.unshift(); + * ``` + */ + $log.log.logs = []; + /** + * @ngdoc property + * @name $log#info.logs + * + * @description + * Array of messages logged using {@link ng.$log#info `info()`}. + * + * @example + * ```js + * $log.info('Some Info'); + * var first = $log.info.logs.unshift(); + * ``` + */ + $log.info.logs = []; + /** + * @ngdoc property + * @name $log#warn.logs + * + * @description + * Array of messages logged using {@link ng.$log#warn `warn()`}. + * + * @example + * ```js + * $log.warn('Some Warning'); + * var first = $log.warn.logs.unshift(); + * ``` + */ + $log.warn.logs = []; + /** + * @ngdoc property + * @name $log#error.logs + * + * @description + * Array of messages logged using {@link ng.$log#error `error()`}. + * + * @example + * ```js + * $log.error('Some Error'); + * var first = $log.error.logs.unshift(); + * ``` + */ + $log.error.logs = []; + /** + * @ngdoc property + * @name $log#debug.logs + * + * @description + * Array of messages logged using {@link ng.$log#debug `debug()`}. + * + * @example + * ```js + * $log.debug('Some Error'); + * var first = $log.debug.logs.unshift(); + * ``` + */ + $log.debug.logs = []; + }; + + /** + * @ngdoc method + * @name $log#assertEmpty + * + * @description + * Assert that all of the logging methods have no logged messages. If any messages are present, + * an exception is thrown. + */ + $log.assertEmpty = function() { + var errors = []; + angular.forEach(['error', 'warn', 'info', 'log', 'debug'], function(logLevel) { + angular.forEach($log[logLevel].logs, function(log) { + angular.forEach(log, function(logItem) { + errors.push('MOCK $log (' + logLevel + '): ' + String(logItem) + '\n' + + (logItem.stack || '')); + }); + }); + }); + if (errors.length) { + errors.unshift('Expected $log to be empty! Either a message was logged unexpectedly, or ' + + 'an expected log message was not checked and removed:'); + errors.push(''); + throw new Error(errors.join('\n---------\n')); + } + }; + + $log.reset(); + return $log; + }; +}; + + +/** + * @ngdoc service + * @name $interval + * + * @description + * Mock implementation of the $interval service. + * + * Use {@link ngMock.$interval#flush `$interval.flush(millis)`} to + * move forward by `millis` milliseconds and trigger any functions scheduled to run in that + * time. + * + * @param {function()} fn A function that should be called repeatedly. + * @param {number} delay Number of milliseconds between each function call. + * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat + * indefinitely. + * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise + * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. + * @param {...*=} Pass additional parameters to the executed function. + * @returns {promise} A promise which will be notified on each iteration. + */ +angular.mock.$IntervalProvider = function() { + this.$get = ['$browser', '$rootScope', '$q', '$$q', + function($browser, $rootScope, $q, $$q) { + var repeatFns = [], + nextRepeatId = 0, + now = 0; + + var $interval = function(fn, delay, count, invokeApply) { + var hasParams = arguments.length > 4, + args = hasParams ? Array.prototype.slice.call(arguments, 4) : [], + iteration = 0, + skipApply = (angular.isDefined(invokeApply) && !invokeApply), + deferred = (skipApply ? $$q : $q).defer(), + promise = deferred.promise; + + count = (angular.isDefined(count)) ? count : 0; + promise.then(null, function() {}, (!hasParams) ? fn : function() { + fn.apply(null, args); + }); + + promise.$$intervalId = nextRepeatId; + + function tick() { + deferred.notify(iteration++); + + if (count > 0 && iteration >= count) { + var fnIndex; + deferred.resolve(iteration); + + angular.forEach(repeatFns, function(fn, index) { + if (fn.id === promise.$$intervalId) fnIndex = index; + }); + + if (angular.isDefined(fnIndex)) { + repeatFns.splice(fnIndex, 1); + } + } + + if (skipApply) { + $browser.defer.flush(); + } else { + $rootScope.$apply(); + } + } + + repeatFns.push({ + nextTime: (now + (delay || 0)), + delay: delay || 1, + fn: tick, + id: nextRepeatId, + deferred: deferred + }); + repeatFns.sort(function(a, b) { return a.nextTime - b.nextTime;}); + + nextRepeatId++; + return promise; + }; + /** + * @ngdoc method + * @name $interval#cancel + * + * @description + * Cancels a task associated with the `promise`. + * + * @param {promise} promise A promise from calling the `$interval` function. + * @returns {boolean} Returns `true` if the task was successfully cancelled. + */ + $interval.cancel = function(promise) { + if (!promise) return false; + var fnIndex; + + angular.forEach(repeatFns, function(fn, index) { + if (fn.id === promise.$$intervalId) fnIndex = index; + }); + + if (angular.isDefined(fnIndex)) { + repeatFns[fnIndex].deferred.promise.then(undefined, function() {}); + repeatFns[fnIndex].deferred.reject('canceled'); + repeatFns.splice(fnIndex, 1); + return true; + } + + return false; + }; + + /** + * @ngdoc method + * @name $interval#flush + * @description + * + * Runs interval tasks scheduled to be run in the next `millis` milliseconds. + * + * @param {number=} millis maximum timeout amount to flush up until. + * + * @return {number} The amount of time moved forward. + */ + $interval.flush = function(millis) { + var before = now; + now += millis; + while (repeatFns.length && repeatFns[0].nextTime <= now) { + var task = repeatFns[0]; + task.fn(); + if (task.nextTime === before) { + // this can only happen the first time + // a zero-delay interval gets triggered + task.nextTime++; + } + task.nextTime += task.delay; + repeatFns.sort(function(a, b) { return a.nextTime - b.nextTime;}); + } + return millis; + }; + + return $interval; + }]; +}; + + +function jsonStringToDate(string) { + // The R_ISO8061_STR regex is never going to fit into the 100 char limit! + // eslit-disable-next-line max-len + var R_ISO8061_STR = /^(-?\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/; + + var match; + if ((match = string.match(R_ISO8061_STR))) { + var date = new Date(0), + tzHour = 0, + tzMin = 0; + if (match[9]) { + tzHour = toInt(match[9] + match[10]); + tzMin = toInt(match[9] + match[11]); + } + date.setUTCFullYear(toInt(match[1]), toInt(match[2]) - 1, toInt(match[3])); + date.setUTCHours(toInt(match[4] || 0) - tzHour, + toInt(match[5] || 0) - tzMin, + toInt(match[6] || 0), + toInt(match[7] || 0)); + return date; + } + return string; +} + +function toInt(str) { + return parseInt(str, 10); +} + +function padNumberInMock(num, digits, trim) { + var neg = ''; + if (num < 0) { + neg = '-'; + num = -num; + } + num = '' + num; + while (num.length < digits) num = '0' + num; + if (trim) { + num = num.substr(num.length - digits); + } + return neg + num; +} + + +/** + * @ngdoc type + * @name angular.mock.TzDate + * @description + * + * *NOTE*: this is not an injectable instance, just a globally available mock class of `Date`. + * + * Mock of the Date type which has its timezone specified via constructor arg. + * + * The main purpose is to create Date-like instances with timezone fixed to the specified timezone + * offset, so that we can test code that depends on local timezone settings without dependency on + * the time zone settings of the machine where the code is running. + * + * @param {number} offset Offset of the *desired* timezone in hours (fractions will be honored) + * @param {(number|string)} timestamp Timestamp representing the desired time in *UTC* + * + * @example + * !!!! WARNING !!!!! + * This is not a complete Date object so only methods that were implemented can be called safely. + * To make matters worse, TzDate instances inherit stuff from Date via a prototype. + * + * We do our best to intercept calls to "unimplemented" methods, but since the list of methods is + * incomplete we might be missing some non-standard methods. This can result in errors like: + * "Date.prototype.foo called on incompatible Object". + * + * ```js + * var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z'); + * newYearInBratislava.getTimezoneOffset() => -60; + * newYearInBratislava.getFullYear() => 2010; + * newYearInBratislava.getMonth() => 0; + * newYearInBratislava.getDate() => 1; + * newYearInBratislava.getHours() => 0; + * newYearInBratislava.getMinutes() => 0; + * newYearInBratislava.getSeconds() => 0; + * ``` + * + */ +angular.mock.TzDate = function(offset, timestamp) { + var self = new Date(0); + if (angular.isString(timestamp)) { + var tsStr = timestamp; + + self.origDate = jsonStringToDate(timestamp); + + timestamp = self.origDate.getTime(); + if (isNaN(timestamp)) { + // eslint-disable-next-line no-throw-literal + throw { + name: 'Illegal Argument', + message: 'Arg \'' + tsStr + '\' passed into TzDate constructor is not a valid date string' + }; + } + } else { + self.origDate = new Date(timestamp); + } + + var localOffset = new Date(timestamp).getTimezoneOffset(); + self.offsetDiff = localOffset * 60 * 1000 - offset * 1000 * 60 * 60; + self.date = new Date(timestamp + self.offsetDiff); + + self.getTime = function() { + return self.date.getTime() - self.offsetDiff; + }; + + self.toLocaleDateString = function() { + return self.date.toLocaleDateString(); + }; + + self.getFullYear = function() { + return self.date.getFullYear(); + }; + + self.getMonth = function() { + return self.date.getMonth(); + }; + + self.getDate = function() { + return self.date.getDate(); + }; + + self.getHours = function() { + return self.date.getHours(); + }; + + self.getMinutes = function() { + return self.date.getMinutes(); + }; + + self.getSeconds = function() { + return self.date.getSeconds(); + }; + + self.getMilliseconds = function() { + return self.date.getMilliseconds(); + }; + + self.getTimezoneOffset = function() { + return offset * 60; + }; + + self.getUTCFullYear = function() { + return self.origDate.getUTCFullYear(); + }; + + self.getUTCMonth = function() { + return self.origDate.getUTCMonth(); + }; + + self.getUTCDate = function() { + return self.origDate.getUTCDate(); + }; + + self.getUTCHours = function() { + return self.origDate.getUTCHours(); + }; + + self.getUTCMinutes = function() { + return self.origDate.getUTCMinutes(); + }; + + self.getUTCSeconds = function() { + return self.origDate.getUTCSeconds(); + }; + + self.getUTCMilliseconds = function() { + return self.origDate.getUTCMilliseconds(); + }; + + self.getDay = function() { + return self.date.getDay(); + }; + + // provide this method only on browsers that already have it + if (self.toISOString) { + self.toISOString = function() { + return padNumberInMock(self.origDate.getUTCFullYear(), 4) + '-' + + padNumberInMock(self.origDate.getUTCMonth() + 1, 2) + '-' + + padNumberInMock(self.origDate.getUTCDate(), 2) + 'T' + + padNumberInMock(self.origDate.getUTCHours(), 2) + ':' + + padNumberInMock(self.origDate.getUTCMinutes(), 2) + ':' + + padNumberInMock(self.origDate.getUTCSeconds(), 2) + '.' + + padNumberInMock(self.origDate.getUTCMilliseconds(), 3) + 'Z'; + }; + } + + //hide all methods not implemented in this mock that the Date prototype exposes + var unimplementedMethods = ['getUTCDay', + 'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds', + 'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear', + 'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds', + 'setYear', 'toDateString', 'toGMTString', 'toJSON', 'toLocaleFormat', 'toLocaleString', + 'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf']; + + angular.forEach(unimplementedMethods, function(methodName) { + self[methodName] = function() { + throw new Error('Method \'' + methodName + '\' is not implemented in the TzDate mock'); + }; + }); + + return self; +}; + +//make "tzDateInstance instanceof Date" return true +angular.mock.TzDate.prototype = Date.prototype; + + +/** + * @ngdoc service + * @name $animate + * + * @description + * Mock implementation of the {@link ng.$animate `$animate`} service. Exposes two additional methods + * for testing animations. + * + * You need to require the `ngAnimateMock` module in your test suite for instance `beforeEach(module('ngAnimateMock'))` + */ +angular.mock.animate = angular.module('ngAnimateMock', ['ng']) + .info({ angularVersion: '"NG_VERSION_FULL"' }) + + .config(['$provide', function($provide) { + + $provide.factory('$$forceReflow', function() { + function reflowFn() { + reflowFn.totalReflows++; + } + reflowFn.totalReflows = 0; + return reflowFn; + }); + + $provide.factory('$$animateAsyncRun', function() { + var queue = []; + var queueFn = function() { + return function(fn) { + queue.push(fn); + }; + }; + queueFn.flush = function() { + if (queue.length === 0) return false; + + for (var i = 0; i < queue.length; i++) { + queue[i](); + } + queue = []; + + return true; + }; + return queueFn; + }); + + $provide.decorator('$$animateJs', ['$delegate', function($delegate) { + var runners = []; + + var animateJsConstructor = function() { + var animator = $delegate.apply($delegate, arguments); + // If no javascript animation is found, animator is undefined + if (animator) { + runners.push(animator); + } + return animator; + }; + + animateJsConstructor.$closeAndFlush = function() { + runners.forEach(function(runner) { + runner.end(); + }); + runners = []; + }; + + return animateJsConstructor; + }]); + + $provide.decorator('$animateCss', ['$delegate', function($delegate) { + var runners = []; + + var animateCssConstructor = function(element, options) { + var animator = $delegate(element, options); + runners.push(animator); + return animator; + }; + + animateCssConstructor.$closeAndFlush = function() { + runners.forEach(function(runner) { + runner.end(); + }); + runners = []; + }; + + return animateCssConstructor; + }]); + + $provide.decorator('$animate', ['$delegate', '$timeout', '$browser', '$$rAF', '$animateCss', '$$animateJs', + '$$forceReflow', '$$animateAsyncRun', '$rootScope', + function($delegate, $timeout, $browser, $$rAF, $animateCss, $$animateJs, + $$forceReflow, $$animateAsyncRun, $rootScope) { + var animate = { + queue: [], + cancel: $delegate.cancel, + on: $delegate.on, + off: $delegate.off, + pin: $delegate.pin, + get reflows() { + return $$forceReflow.totalReflows; + }, + enabled: $delegate.enabled, + /** + * @ngdoc method + * @name $animate#closeAndFlush + * @description + * + * This method will close all pending animations (both {@link ngAnimate#javascript-based-animations Javascript} + * and {@link ngAnimate.$animateCss CSS}) and it will also flush any remaining animation frames and/or callbacks. + */ + closeAndFlush: function() { + // we allow the flush command to swallow the errors + // because depending on whether CSS or JS animations are + // used, there may not be a RAF flush. The primary flush + // at the end of this function must throw an exception + // because it will track if there were pending animations + this.flush(true); + $animateCss.$closeAndFlush(); + $$animateJs.$closeAndFlush(); + this.flush(); + }, + /** + * @ngdoc method + * @name $animate#flush + * @description + * + * This method is used to flush the pending callbacks and animation frames to either start + * an animation or conclude an animation. Note that this will not actually close an + * actively running animation (see {@link ngMock.$animate#closeAndFlush `closeAndFlush()`} for that). + */ + flush: function(hideErrors) { + $rootScope.$digest(); + + var doNextRun, somethingFlushed = false; + do { + doNextRun = false; + + if ($$rAF.queue.length) { + $$rAF.flush(); + doNextRun = somethingFlushed = true; + } + + if ($$animateAsyncRun.flush()) { + doNextRun = somethingFlushed = true; + } + } while (doNextRun); + + if (!somethingFlushed && !hideErrors) { + throw new Error('No pending animations ready to be closed or flushed'); + } + + $rootScope.$digest(); + } + }; + + angular.forEach( + ['animate','enter','leave','move','addClass','removeClass','setClass'], function(method) { + animate[method] = function() { + animate.queue.push({ + event: method, + element: arguments[0], + options: arguments[arguments.length - 1], + args: arguments + }); + return $delegate[method].apply($delegate, arguments); + }; + }); + + return animate; + }]); + + }]); + + +/** + * @ngdoc function + * @name angular.mock.dump + * @description + * + * *NOTE*: This is not an injectable instance, just a globally available function. + * + * Method for serializing common AngularJS objects (scope, elements, etc..) into strings. + * It is useful for logging objects to the console when debugging. + * + * @param {*} object - any object to turn into string. + * @return {string} a serialized string of the argument + */ +angular.mock.dump = function(object) { + return serialize(object); + + function serialize(object) { + var out; + + if (angular.isElement(object)) { + object = angular.element(object); + out = angular.element('
'); + angular.forEach(object, function(element) { + out.append(angular.element(element).clone()); + }); + out = out.html(); + } else if (angular.isArray(object)) { + out = []; + angular.forEach(object, function(o) { + out.push(serialize(o)); + }); + out = '[ ' + out.join(', ') + ' ]'; + } else if (angular.isObject(object)) { + if (angular.isFunction(object.$eval) && angular.isFunction(object.$apply)) { + out = serializeScope(object); + } else if (object instanceof Error) { + out = object.stack || ('' + object.name + ': ' + object.message); + } else { + // TODO(i): this prevents methods being logged, + // we should have a better way to serialize objects + out = angular.toJson(object, true); + } + } else { + out = String(object); + } + + return out; + } + + function serializeScope(scope, offset) { + offset = offset || ' '; + var log = [offset + 'Scope(' + scope.$id + '): {']; + for (var key in scope) { + if (Object.prototype.hasOwnProperty.call(scope, key) && !key.match(/^(\$|this)/)) { + log.push(' ' + key + ': ' + angular.toJson(scope[key])); + } + } + var child = scope.$$childHead; + while (child) { + log.push(serializeScope(child, offset + ' ')); + child = child.$$nextSibling; + } + log.push('}'); + return log.join('\n' + offset); + } +}; + +/** + * @ngdoc service + * @name $httpBackend + * @description + * Fake HTTP backend implementation suitable for unit testing applications that use the + * {@link ng.$http $http service}. + * + *
+ * **Note**: For fake HTTP backend implementation suitable for end-to-end testing or backend-less + * development please see {@link ngMockE2E.$httpBackend e2e $httpBackend mock}. + *
+ * + * During unit testing, we want our unit tests to run quickly and have no external dependencies so + * we don’t want to send [XHR](https://developer.mozilla.org/en/xmlhttprequest) or + * [JSONP](http://en.wikipedia.org/wiki/JSONP) requests to a real server. All we really need is + * to verify whether a certain request has been sent or not, or alternatively just let the + * application make requests, respond with pre-trained responses and assert that the end result is + * what we expect it to be. + * + * This mock implementation can be used to respond with static or dynamic responses via the + * `expect` and `when` apis and their shortcuts (`expectGET`, `whenPOST`, etc). + * + * When an AngularJS application needs some data from a server, it calls the $http service, which + * sends the request to a real server using $httpBackend service. With dependency injection, it is + * easy to inject $httpBackend mock (which has the same API as $httpBackend) and use it to verify + * the requests and respond with some testing data without sending a request to a real server. + * + * There are two ways to specify what test data should be returned as http responses by the mock + * backend when the code under test makes http requests: + * + * - `$httpBackend.expect` - specifies a request expectation + * - `$httpBackend.when` - specifies a backend definition + * + * + * ## Request Expectations vs Backend Definitions + * + * Request expectations provide a way to make assertions about requests made by the application and + * to define responses for those requests. The test will fail if the expected requests are not made + * or they are made in the wrong order. + * + * Backend definitions allow you to define a fake backend for your application which doesn't assert + * if a particular request was made or not, it just returns a trained response if a request is made. + * The test will pass whether or not the request gets made during testing. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Request expectationsBackend definitions
Syntax.expect(...).respond(...).when(...).respond(...)
Typical usagestrict unit testsloose (black-box) unit testing
Fulfills multiple requestsNOYES
Order of requests mattersYESNO
Request requiredYESNO
Response requiredoptional (see below)YES
+ * + * In cases where both backend definitions and request expectations are specified during unit + * testing, the request expectations are evaluated first. + * + * If a request expectation has no response specified, the algorithm will search your backend + * definitions for an appropriate response. + * + * If a request didn't match any expectation or if the expectation doesn't have the response + * defined, the backend definitions are evaluated in sequential order to see if any of them match + * the request. The response from the first matched definition is returned. + * + * + * ## Flushing HTTP requests + * + * The $httpBackend used in production always responds to requests asynchronously. If we preserved + * this behavior in unit testing, we'd have to create async unit tests, which are hard to write, + * to follow and to maintain. But neither can the testing mock respond synchronously; that would + * change the execution of the code under test. For this reason, the mock $httpBackend has a + * `flush()` method, which allows the test to explicitly flush pending requests. This preserves + * the async api of the backend, while allowing the test to execute synchronously. + * + * + * ## Unit testing with mock $httpBackend + * The following code shows how to setup and use the mock backend when unit testing a controller. + * First we create the controller under test: + * + ```js + // The module code + angular + .module('MyApp', []) + .controller('MyController', MyController); + + // The controller code + function MyController($scope, $http) { + var authToken; + + $http.get('/auth.py').then(function(response) { + authToken = response.headers('A-Token'); + $scope.user = response.data; + }).catch(function() { + $scope.status = 'Failed...'; + }); + + $scope.saveMessage = function(message) { + var headers = { 'Authorization': authToken }; + $scope.status = 'Saving...'; + + $http.post('/add-msg.py', message, { headers: headers } ).then(function(response) { + $scope.status = ''; + }).catch(function() { + $scope.status = 'Failed...'; + }); + }; + } + ``` + * + * Now we setup the mock backend and create the test specs: + * + ```js + // testing controller + describe('MyController', function() { + var $httpBackend, $rootScope, createController, authRequestHandler; + + // Set up the module + beforeEach(module('MyApp')); + + beforeEach(inject(function($injector) { + // Set up the mock http service responses + $httpBackend = $injector.get('$httpBackend'); + // backend definition common for all tests + authRequestHandler = $httpBackend.when('GET', '/auth.py') + .respond({userId: 'userX'}, {'A-Token': 'xxx'}); + + // Get hold of a scope (i.e. the root scope) + $rootScope = $injector.get('$rootScope'); + // The $controller service is used to create instances of controllers + var $controller = $injector.get('$controller'); + + createController = function() { + return $controller('MyController', {'$scope' : $rootScope }); + }; + })); + + + afterEach(function() { + $httpBackend.verifyNoOutstandingExpectation(); + $httpBackend.verifyNoOutstandingRequest(); + }); + + + it('should fetch authentication token', function() { + $httpBackend.expectGET('/auth.py'); + var controller = createController(); + $httpBackend.flush(); + }); + + + it('should fail authentication', function() { + + // Notice how you can change the response even after it was set + authRequestHandler.respond(401, ''); + + $httpBackend.expectGET('/auth.py'); + var controller = createController(); + $httpBackend.flush(); + expect($rootScope.status).toBe('Failed...'); + }); + + + it('should send msg to server', function() { + var controller = createController(); + $httpBackend.flush(); + + // now you don’t care about the authentication, but + // the controller will still send the request and + // $httpBackend will respond without you having to + // specify the expectation and response for this request + + $httpBackend.expectPOST('/add-msg.py', 'message content').respond(201, ''); + $rootScope.saveMessage('message content'); + expect($rootScope.status).toBe('Saving...'); + $httpBackend.flush(); + expect($rootScope.status).toBe(''); + }); + + + it('should send auth header', function() { + var controller = createController(); + $httpBackend.flush(); + + $httpBackend.expectPOST('/add-msg.py', undefined, function(headers) { + // check if the header was sent, if it wasn't the expectation won't + // match the request and the test will fail + return headers['Authorization'] === 'xxx'; + }).respond(201, ''); + + $rootScope.saveMessage('whatever'); + $httpBackend.flush(); + }); + }); + ``` + * + * ## Dynamic responses + * + * You define a response to a request by chaining a call to `respond()` onto a definition or expectation. + * If you provide a **callback** as the first parameter to `respond(callback)` then you can dynamically generate + * a response based on the properties of the request. + * + * The `callback` function should be of the form `function(method, url, data, headers, params)`. + * + * ### Query parameters + * + * By default, query parameters on request URLs are parsed into the `params` object. So a request URL + * of `/list?q=searchstr&orderby=-name` would set `params` to be `{q: 'searchstr', orderby: '-name'}`. + * + * ### Regex parameter matching + * + * If an expectation or definition uses a **regex** to match the URL, you can provide an array of **keys** via a + * `params` argument. The index of each **key** in the array will match the index of a **group** in the + * **regex**. + * + * The `params` object in the **callback** will now have properties with these keys, which hold the value of the + * corresponding **group** in the **regex**. + * + * This also applies to the `when` and `expect` shortcut methods. + * + * + * ```js + * $httpBackend.expect('GET', /\/user\/(.+)/, undefined, undefined, ['id']) + * .respond(function(method, url, data, headers, params) { + * // for requested url of '/user/1234' params is {id: '1234'} + * }); + * + * $httpBackend.whenPATCH(/\/user\/(.+)\/article\/(.+)/, undefined, undefined, ['user', 'article']) + * .respond(function(method, url, data, headers, params) { + * // for url of '/user/1234/article/567' params is {user: '1234', article: '567'} + * }); + * ``` + * + * ## Matching route requests + * + * For extra convenience, `whenRoute` and `expectRoute` shortcuts are available. These methods offer colon + * delimited matching of the url path, ignoring the query string. This allows declarations + * similar to how application routes are configured with `$routeProvider`. Because these methods convert + * the definition url to regex, declaration order is important. Combined with query parameter parsing, + * the following is possible: + * + ```js + $httpBackend.whenRoute('GET', '/users/:id') + .respond(function(method, url, data, headers, params) { + return [200, MockUserList[Number(params.id)]]; + }); + + $httpBackend.whenRoute('GET', '/users') + .respond(function(method, url, data, headers, params) { + var userList = angular.copy(MockUserList), + defaultSort = 'lastName', + count, pages, isPrevious, isNext; + + // paged api response '/v1/users?page=2' + params.page = Number(params.page) || 1; + + // query for last names '/v1/users?q=Archer' + if (params.q) { + userList = $filter('filter')({lastName: params.q}); + } + + pages = Math.ceil(userList.length / pagingLength); + isPrevious = params.page > 1; + isNext = params.page < pages; + + return [200, { + count: userList.length, + previous: isPrevious, + next: isNext, + // sort field -> '/v1/users?sortBy=firstName' + results: $filter('orderBy')(userList, params.sortBy || defaultSort) + .splice((params.page - 1) * pagingLength, pagingLength) + }]; + }); + ``` + */ +angular.mock.$httpBackendDecorator = + ['$rootScope', '$timeout', '$delegate', createHttpBackendMock]; + +/** + * General factory function for $httpBackend mock. + * Returns instance for unit testing (when no arguments specified): + * - passing through is disabled + * - auto flushing is disabled + * + * Returns instance for e2e testing (when `$delegate` and `$browser` specified): + * - passing through (delegating request to real backend) is enabled + * - auto flushing is enabled + * + * @param {Object=} $delegate Real $httpBackend instance (allow passing through if specified) + * @param {Object=} $browser Auto-flushing enabled if specified + * @return {Object} Instance of $httpBackend mock + */ +function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { + var definitions = [], + expectations = [], + responses = [], + responsesPush = angular.bind(responses, responses.push), + copy = angular.copy, + // We cache the original backend so that if both ngMock and ngMockE2E override the + // service the ngMockE2E version can pass through to the real backend + originalHttpBackend = $delegate.$$originalHttpBackend || $delegate; + + function createResponse(status, data, headers, statusText) { + if (angular.isFunction(status)) return status; + + return function() { + return angular.isNumber(status) + ? [status, data, headers, statusText, 'complete'] + : [200, status, data, headers, 'complete']; + }; + } + + // TODO(vojta): change params to: method, url, data, headers, callback + function $httpBackend(method, url, data, callback, headers, timeout, withCredentials, responseType, eventHandlers, uploadEventHandlers) { + + var xhr = new MockXhr(), + expectation = expectations[0], + wasExpected = false; + + xhr.$$events = eventHandlers; + xhr.upload.$$events = uploadEventHandlers; + + function prettyPrint(data) { + return (angular.isString(data) || angular.isFunction(data) || data instanceof RegExp) + ? data + : angular.toJson(data); + } + + function wrapResponse(wrapped) { + if (!$browser && timeout) { + if (timeout.then) { + timeout.then(handleTimeout); + } else { + $timeout(handleTimeout, timeout); + } + } + + handleResponse.description = method + ' ' + url; + return handleResponse; + + function handleResponse() { + var response = wrapped.response(method, url, data, headers, wrapped.params(url)); + xhr.$$respHeaders = response[2]; + callback(copy(response[0]), copy(response[1]), xhr.getAllResponseHeaders(), + copy(response[3] || ''), copy(response[4])); + } + + function handleTimeout() { + for (var i = 0, ii = responses.length; i < ii; i++) { + if (responses[i] === handleResponse) { + responses.splice(i, 1); + callback(-1, undefined, '', undefined, 'timeout'); + break; + } + } + } + } + + if (expectation && expectation.match(method, url)) { + if (!expectation.matchData(data)) { + throw new Error('Expected ' + expectation + ' with different data\n' + + 'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data); + } + + if (!expectation.matchHeaders(headers)) { + throw new Error('Expected ' + expectation + ' with different headers\n' + + 'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' + + prettyPrint(headers)); + } + + expectations.shift(); + + if (expectation.response) { + responses.push(wrapResponse(expectation)); + return; + } + wasExpected = true; + } + + var i = -1, definition; + while ((definition = definitions[++i])) { + if (definition.match(method, url, data, headers || {})) { + if (definition.response) { + // if $browser specified, we do auto flush all requests + ($browser ? $browser.defer : responsesPush)(wrapResponse(definition)); + } else if (definition.passThrough) { + originalHttpBackend(method, url, data, callback, headers, timeout, withCredentials, responseType, eventHandlers, uploadEventHandlers); + } else throw new Error('No response defined !'); + return; + } + } + var error = wasExpected ? + new Error('No response defined !') : + new Error('Unexpected request: ' + method + ' ' + url + '\n' + + (expectation ? 'Expected ' + expectation : 'No more request expected')); + + // In addition to be being converted to a rejection, this error also needs to be passed to + // the $exceptionHandler and be rethrown (so that the test fails). + error.$$passToExceptionHandler = true; + + throw error; + } + + /** + * @ngdoc method + * @name $httpBackend#when + * @description + * Creates a new backend definition. + * + * @param {string} method HTTP method. + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives + * data string and returns true if the data is as expected. + * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header + * object and returns true if the headers match the current definition. + * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + * + * - respond – + * ```js + * {function([status,] data[, headers, statusText]) + * | function(function(method, url, data, headers, params)} + * ``` + * – The respond method takes a set of static data to be returned or a function that can + * return an array containing response status (number), response data (Array|Object|string), + * response headers (Object), and the text for the status (string). The respond method returns + * the `requestHandler` object for possible overrides. + */ + $httpBackend.when = function(method, url, data, headers, keys) { + + assertArgDefined(arguments, 1, 'url'); + + var definition = new MockHttpExpectation(method, url, data, headers, keys), + chain = { + respond: function(status, data, headers, statusText) { + definition.passThrough = undefined; + definition.response = createResponse(status, data, headers, statusText); + return chain; + } + }; + + if ($browser) { + chain.passThrough = function() { + definition.response = undefined; + definition.passThrough = true; + return chain; + }; + } + + definitions.push(definition); + return chain; + }; + + /** + * @ngdoc method + * @name $httpBackend#whenGET + * @description + * Creates a new backend definition for GET requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(Object|function(Object))=} headers HTTP headers. + * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenHEAD + * @description + * Creates a new backend definition for HEAD requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(Object|function(Object))=} headers HTTP headers. + * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenDELETE + * @description + * Creates a new backend definition for DELETE requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(Object|function(Object))=} headers HTTP headers. + * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenPOST + * @description + * Creates a new backend definition for POST requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives + * data string and returns true if the data is as expected. + * @param {(Object|function(Object))=} headers HTTP headers. + * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenPUT + * @description + * Creates a new backend definition for PUT requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives + * data string and returns true if the data is as expected. + * @param {(Object|function(Object))=} headers HTTP headers. + * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenJSONP + * @description + * Creates a new backend definition for JSONP requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + createShortMethods('when'); + + /** + * @ngdoc method + * @name $httpBackend#whenRoute + * @description + * Creates a new backend definition that compares only with the requested route. + * + * @param {string} method HTTP method. + * @param {string} url HTTP url string that supports colon param matching. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. See #when for more info. + */ + $httpBackend.whenRoute = function(method, url) { + var pathObj = parseRoute(url); + return $httpBackend.when(method, pathObj.regexp, undefined, undefined, pathObj.keys); + }; + + function parseRoute(url) { + var ret = { + regexp: url + }, + keys = ret.keys = []; + + if (!url || !angular.isString(url)) return ret; + + url = url + .replace(/([().])/g, '\\$1') + .replace(/(\/)?:(\w+)([?*])?/g, function(_, slash, key, option) { + var optional = option === '?' ? option : null; + var star = option === '*' ? option : null; + keys.push({ name: key, optional: !!optional }); + slash = slash || ''; + return '' + + (optional ? '' : slash) + + '(?:' + + (optional ? slash : '') + + (star && '(.+?)' || '([^/]+)') + + (optional || '') + + ')' + + (optional || ''); + }) + .replace(/([/$*])/g, '\\$1'); + + ret.regexp = new RegExp('^' + url, 'i'); + return ret; + } + + /** + * @ngdoc method + * @name $httpBackend#expect + * @description + * Creates a new request expectation. + * + * @param {string} method HTTP method. + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that + * receives data string and returns true if the data is as expected, or Object if request body + * is in JSON format. + * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header + * object and returns true if the headers match the current expectation. + * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + * + * - respond – + * ``` + * { function([status,] data[, headers, statusText]) + * | function(function(method, url, data, headers, params)} + * ``` + * – The respond method takes a set of static data to be returned or a function that can + * return an array containing response status (number), response data (Array|Object|string), + * response headers (Object), and the text for the status (string). The respond method returns + * the `requestHandler` object for possible overrides. + */ + $httpBackend.expect = function(method, url, data, headers, keys) { + + assertArgDefined(arguments, 1, 'url'); + + var expectation = new MockHttpExpectation(method, url, data, headers, keys), + chain = { + respond: function(status, data, headers, statusText) { + expectation.response = createResponse(status, data, headers, statusText); + return chain; + } + }; + + expectations.push(expectation); + return chain; + }; + + /** + * @ngdoc method + * @name $httpBackend#expectGET + * @description + * Creates a new request expectation for GET requests. For more info see `expect()`. + * + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {Object=} headers HTTP headers. + * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. See #expect for more info. + */ + + /** + * @ngdoc method + * @name $httpBackend#expectHEAD + * @description + * Creates a new request expectation for HEAD requests. For more info see `expect()`. + * + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {Object=} headers HTTP headers. + * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#expectDELETE + * @description + * Creates a new request expectation for DELETE requests. For more info see `expect()`. + * + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {Object=} headers HTTP headers. + * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#expectPOST + * @description + * Creates a new request expectation for POST requests. For more info see `expect()`. + * + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that + * receives data string and returns true if the data is as expected, or Object if request body + * is in JSON format. + * @param {Object=} headers HTTP headers. + * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#expectPUT + * @description + * Creates a new request expectation for PUT requests. For more info see `expect()`. + * + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that + * receives data string and returns true if the data is as expected, or Object if request body + * is in JSON format. + * @param {Object=} headers HTTP headers. + * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#expectPATCH + * @description + * Creates a new request expectation for PATCH requests. For more info see `expect()`. + * + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that + * receives data string and returns true if the data is as expected, or Object if request body + * is in JSON format. + * @param {Object=} headers HTTP headers. + * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#expectJSONP + * @description + * Creates a new request expectation for JSONP requests. For more info see `expect()`. + * + * @param {string|RegExp|function(string)=} url HTTP url or function that receives an url + * and returns true if the url matches the current definition. + * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + createShortMethods('expect'); + + /** + * @ngdoc method + * @name $httpBackend#expectRoute + * @description + * Creates a new request expectation that compares only with the requested route. + * + * @param {string} method HTTP method. + * @param {string} url HTTP url string that supports colon param matching. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. See #expect for more info. + */ + $httpBackend.expectRoute = function(method, url) { + var pathObj = parseRoute(url); + return $httpBackend.expect(method, pathObj.regexp, undefined, undefined, pathObj.keys); + }; + + + /** + * @ngdoc method + * @name $httpBackend#flush + * @description + * Flushes pending requests using the trained responses. Requests are flushed in the order they + * were made, but it is also possible to skip one or more requests (for example to have them + * flushed later). This is useful for simulating scenarios where responses arrive from the server + * in any order. + * + * If there are no pending requests to flush when the method is called, an exception is thrown (as + * this is typically a sign of programming error). + * + * @param {number=} count - Number of responses to flush. If undefined/null, all pending requests + * (starting after `skip`) will be flushed. + * @param {number=} [skip=0] - Number of pending requests to skip. For example, a value of `5` + * would skip the first 5 pending requests and start flushing from the 6th onwards. + */ + $httpBackend.flush = function(count, skip, digest) { + if (digest !== false) $rootScope.$digest(); + + skip = skip || 0; + if (skip >= responses.length) throw new Error('No pending request to flush !'); + + if (angular.isDefined(count) && count !== null) { + while (count--) { + var part = responses.splice(skip, 1); + if (!part.length) throw new Error('No more pending request to flush !'); + part[0](); + } + } else { + while (responses.length > skip) { + responses.splice(skip, 1)[0](); + } + } + $httpBackend.verifyNoOutstandingExpectation(digest); + }; + + + /** + * @ngdoc method + * @name $httpBackend#verifyNoOutstandingExpectation + * @description + * Verifies that all of the requests defined via the `expect` api were made. If any of the + * requests were not made, verifyNoOutstandingExpectation throws an exception. + * + * Typically, you would call this method following each test case that asserts requests using an + * "afterEach" clause. + * + * ```js + * afterEach($httpBackend.verifyNoOutstandingExpectation); + * ``` + */ + $httpBackend.verifyNoOutstandingExpectation = function(digest) { + if (digest !== false) $rootScope.$digest(); + if (expectations.length) { + throw new Error('Unsatisfied requests: ' + expectations.join(', ')); + } + }; + + + /** + * @ngdoc method + * @name $httpBackend#verifyNoOutstandingRequest + * @description + * Verifies that there are no outstanding requests that need to be flushed. + * + * Typically, you would call this method following each test case that asserts requests using an + * "afterEach" clause. + * + * ```js + * afterEach($httpBackend.verifyNoOutstandingRequest); + * ``` + */ + $httpBackend.verifyNoOutstandingRequest = function(digest) { + if (digest !== false) $rootScope.$digest(); + if (responses.length) { + var unflushedDescriptions = responses.map(function(res) { return res.description; }); + throw new Error('Unflushed requests: ' + responses.length + '\n ' + + unflushedDescriptions.join('\n ')); + } + }; + + + /** + * @ngdoc method + * @name $httpBackend#resetExpectations + * @description + * Resets all request expectations, but preserves all backend definitions. Typically, you would + * call resetExpectations during a multiple-phase test when you want to reuse the same instance of + * $httpBackend mock. + */ + $httpBackend.resetExpectations = function() { + expectations.length = 0; + responses.length = 0; + }; + + $httpBackend.$$originalHttpBackend = originalHttpBackend; + + return $httpBackend; + + + function createShortMethods(prefix) { + angular.forEach(['GET', 'DELETE', 'JSONP', 'HEAD'], function(method) { + $httpBackend[prefix + method] = function(url, headers, keys) { + assertArgDefined(arguments, 0, 'url'); + + // Change url to `null` if `undefined` to stop it throwing an exception further down + if (angular.isUndefined(url)) url = null; + + return $httpBackend[prefix](method, url, undefined, headers, keys); + }; + }); + + angular.forEach(['PUT', 'POST', 'PATCH'], function(method) { + $httpBackend[prefix + method] = function(url, data, headers, keys) { + assertArgDefined(arguments, 0, 'url'); + + // Change url to `null` if `undefined` to stop it throwing an exception further down + if (angular.isUndefined(url)) url = null; + + return $httpBackend[prefix](method, url, data, headers, keys); + }; + }); + } +} + +function assertArgDefined(args, index, name) { + if (args.length > index && angular.isUndefined(args[index])) { + throw new Error('Undefined argument `' + name + '`; the argument is provided but not defined'); + } +} + + +function MockHttpExpectation(method, url, data, headers, keys) { + + function getUrlParams(u) { + var params = u.slice(u.indexOf('?') + 1).split('&'); + return params.sort(); + } + + function compareUrl(u) { + return (url.slice(0, url.indexOf('?')) === u.slice(0, u.indexOf('?')) && + getUrlParams(url).join() === getUrlParams(u).join()); + } + + this.data = data; + this.headers = headers; + + this.match = function(m, u, d, h) { + if (method !== m) return false; + if (!this.matchUrl(u)) return false; + if (angular.isDefined(d) && !this.matchData(d)) return false; + if (angular.isDefined(h) && !this.matchHeaders(h)) return false; + return true; + }; + + this.matchUrl = function(u) { + if (!url) return true; + if (angular.isFunction(url.test)) return url.test(u); + if (angular.isFunction(url)) return url(u); + return (url === u || compareUrl(u)); + }; + + this.matchHeaders = function(h) { + if (angular.isUndefined(headers)) return true; + if (angular.isFunction(headers)) return headers(h); + return angular.equals(headers, h); + }; + + this.matchData = function(d) { + if (angular.isUndefined(data)) return true; + if (data && angular.isFunction(data.test)) return data.test(d); + if (data && angular.isFunction(data)) return data(d); + if (data && !angular.isString(data)) { + return angular.equals(angular.fromJson(angular.toJson(data)), angular.fromJson(d)); + } + // eslint-disable-next-line eqeqeq + return data == d; + }; + + this.toString = function() { + return method + ' ' + url; + }; + + this.params = function(u) { + return angular.extend(parseQuery(), pathParams()); + + function pathParams() { + var keyObj = {}; + if (!url || !angular.isFunction(url.test) || !keys || keys.length === 0) return keyObj; + + var m = url.exec(u); + if (!m) return keyObj; + for (var i = 1, len = m.length; i < len; ++i) { + var key = keys[i - 1]; + var val = m[i]; + if (key && val) { + keyObj[key.name || key] = val; + } + } + + return keyObj; + } + + function parseQuery() { + var obj = {}, key_value, key, + queryStr = u.indexOf('?') > -1 + ? u.substring(u.indexOf('?') + 1) + : ''; + + angular.forEach(queryStr.split('&'), function(keyValue) { + if (keyValue) { + key_value = keyValue.replace(/\+/g,'%20').split('='); + key = tryDecodeURIComponent(key_value[0]); + if (angular.isDefined(key)) { + var val = angular.isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true; + if (!hasOwnProperty.call(obj, key)) { + obj[key] = val; + } else if (angular.isArray(obj[key])) { + obj[key].push(val); + } else { + obj[key] = [obj[key],val]; + } + } + } + }); + return obj; + } + function tryDecodeURIComponent(value) { + try { + return decodeURIComponent(value); + } catch (e) { + // Ignore any invalid uri component + } + } + }; +} + +function createMockXhr() { + return new MockXhr(); +} + +function MockXhr() { + + // hack for testing $http, $httpBackend + MockXhr.$$lastInstance = this; + + this.open = function(method, url, async) { + this.$$method = method; + this.$$url = url; + this.$$async = async; + this.$$reqHeaders = {}; + this.$$respHeaders = {}; + }; + + this.send = function(data) { + this.$$data = data; + }; + + this.setRequestHeader = function(key, value) { + this.$$reqHeaders[key] = value; + }; + + this.getResponseHeader = function(name) { + // the lookup must be case insensitive, + // that's why we try two quick lookups first and full scan last + var header = this.$$respHeaders[name]; + if (header) return header; + + name = angular.$$lowercase(name); + header = this.$$respHeaders[name]; + if (header) return header; + + header = undefined; + angular.forEach(this.$$respHeaders, function(headerVal, headerName) { + if (!header && angular.$$lowercase(headerName) === name) header = headerVal; + }); + return header; + }; + + this.getAllResponseHeaders = function() { + var lines = []; + + angular.forEach(this.$$respHeaders, function(value, key) { + lines.push(key + ': ' + value); + }); + return lines.join('\n'); + }; + + this.abort = angular.noop; + + // This section simulates the events on a real XHR object (and the upload object) + // When we are testing $httpBackend (inside the AngularJS project) we make partial use of this + // but store the events directly ourselves on `$$events`, instead of going through the `addEventListener` + this.$$events = {}; + this.addEventListener = function(name, listener) { + if (angular.isUndefined(this.$$events[name])) this.$$events[name] = []; + this.$$events[name].push(listener); + }; + + this.upload = { + $$events: {}, + addEventListener: this.addEventListener + }; +} + + +/** + * @ngdoc service + * @name $timeout + * @description + * + * This service is just a simple decorator for {@link ng.$timeout $timeout} service + * that adds a "flush" and "verifyNoPendingTasks" methods. + */ + +angular.mock.$TimeoutDecorator = ['$delegate', '$browser', function($delegate, $browser) { + + /** + * @ngdoc method + * @name $timeout#flush + * @description + * + * Flushes the queue of pending tasks. + * + * @param {number=} delay maximum timeout amount to flush up until + */ + $delegate.flush = function(delay) { + $browser.defer.flush(delay); + }; + + /** + * @ngdoc method + * @name $timeout#verifyNoPendingTasks + * @description + * + * Verifies that there are no pending tasks that need to be flushed. + */ + $delegate.verifyNoPendingTasks = function() { + if ($browser.deferredFns.length) { + throw new Error('Deferred tasks to flush (' + $browser.deferredFns.length + '): ' + + formatPendingTasksAsString($browser.deferredFns)); + } + }; + + function formatPendingTasksAsString(tasks) { + var result = []; + angular.forEach(tasks, function(task) { + result.push('{id: ' + task.id + ', time: ' + task.time + '}'); + }); + + return result.join(', '); + } + + return $delegate; +}]; + +angular.mock.$RAFDecorator = ['$delegate', function($delegate) { + var rafFn = function(fn) { + var index = rafFn.queue.length; + rafFn.queue.push(fn); + return function() { + rafFn.queue.splice(index, 1); + }; + }; + + rafFn.queue = []; + rafFn.supported = $delegate.supported; + + rafFn.flush = function() { + if (rafFn.queue.length === 0) { + throw new Error('No rAF callbacks present'); + } + + var length = rafFn.queue.length; + for (var i = 0; i < length; i++) { + rafFn.queue[i](); + } + + rafFn.queue = rafFn.queue.slice(i); + }; + + return rafFn; +}]; + +/** + * + */ +var originalRootElement; +angular.mock.$RootElementProvider = function() { + this.$get = ['$injector', function($injector) { + originalRootElement = angular.element('
').data('$injector', $injector); + return originalRootElement; + }]; +}; + +/** + * @ngdoc service + * @name $controller + * @description + * A decorator for {@link ng.$controller} with additional `bindings` parameter, useful when testing + * controllers of directives that use {@link $compile#-bindtocontroller- `bindToController`}. + * + * ## Example + * + * ```js + * + * // Directive definition ... + * + * myMod.directive('myDirective', { + * controller: 'MyDirectiveController', + * bindToController: { + * name: '@' + * } + * }); + * + * + * // Controller definition ... + * + * myMod.controller('MyDirectiveController', ['$log', function($log) { + * this.log = function() { + * $log.info(this.name); + * }; + * }]); + * + * + * // In a test ... + * + * describe('myDirectiveController', function() { + * describe('log()', function() { + * it('should write the bound name to the log', inject(function($controller, $log) { + * var ctrl = $controller('MyDirectiveController', { /* no locals */ }, { name: 'Clark Kent' }); + * ctrl.log(); + * + * expect(ctrl.name).toEqual('Clark Kent'); + * expect($log.info.logs).toEqual(['Clark Kent']); + * })); + * }); + * }); + * + * ``` + * + * @param {Function|string} constructor If called with a function then it's considered to be the + * controller constructor function. Otherwise it's considered to be a string which is used + * to retrieve the controller constructor using the following steps: + * + * * check if a controller with given name is registered via `$controllerProvider` + * * check if evaluating the string on the current scope returns a constructor + * + * The string can use the `controller as property` syntax, where the controller instance is published + * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this + * to work correctly. + * + * @param {Object} locals Injection locals for Controller. + * @param {Object=} bindings Properties to add to the controller instance. This is used to simulate + * the `bindToController` feature and simplify certain kinds of tests. + * @return {Object} Instance of given controller. + */ +function createControllerDecorator() { + angular.mock.$ControllerDecorator = ['$delegate', function($delegate) { + return function(expression, locals, later, ident) { + if (later && typeof later === 'object') { + var instantiate = $delegate(expression, locals, true, ident); + var instance = instantiate(); + angular.extend(instance, later); + return instance; + } + return $delegate(expression, locals, later, ident); + }; + }]; + + return angular.mock.$ControllerDecorator; +} + +/** + * @ngdoc service + * @name $componentController + * @description + * A service that can be used to create instances of component controllers. Useful for unit-testing. + * + * Be aware that the controller will be instantiated and attached to the scope as specified in + * the component definition object. If you do not provide a `$scope` object in the `locals` param + * then the helper will create a new isolated scope as a child of `$rootScope`. + * + * If you are using `$element` or `$attrs` in the controller, make sure to provide them as `locals`. + * The `$element` must be a jqLite-wrapped DOM element, and `$attrs` should be an object that + * has all properties / functions that you are using in the controller. If this is getting too complex, + * you should compile the component instead and access the component's controller via the + * {@link angular.element#methods `controller`} function. + * + * See also the section on {@link guide/component#unit-testing-component-controllers unit-testing component controllers} + * in the guide. + * + * @param {string} componentName the name of the component whose controller we want to instantiate + * @param {Object} locals Injection locals for Controller. + * @param {Object=} bindings Properties to add to the controller before invoking the constructor. This is used + * to simulate the `bindToController` feature and simplify certain kinds of tests. + * @param {string=} ident Override the property name to use when attaching the controller to the scope. + * @return {Object} Instance of requested controller. + */ +angular.mock.$ComponentControllerProvider = ['$compileProvider', + function ComponentControllerProvider($compileProvider) { + this.$get = ['$controller','$injector', '$rootScope', function($controller, $injector, $rootScope) { + return function $componentController(componentName, locals, bindings, ident) { + // get all directives associated to the component name + var directives = $injector.get(componentName + 'Directive'); + // look for those directives that are components + var candidateDirectives = directives.filter(function(directiveInfo) { + // components have controller, controllerAs and restrict:'E' + return directiveInfo.controller && directiveInfo.controllerAs && directiveInfo.restrict === 'E'; + }); + // check if valid directives found + if (candidateDirectives.length === 0) { + throw new Error('No component found'); + } + if (candidateDirectives.length > 1) { + throw new Error('Too many components found'); + } + // get the info of the component + var directiveInfo = candidateDirectives[0]; + // create a scope if needed + locals = locals || {}; + locals.$scope = locals.$scope || $rootScope.$new(true); + return $controller(directiveInfo.controller, locals, bindings, ident || directiveInfo.controllerAs); + }; + }]; +}]; + + +/** + * @ngdoc module + * @name ngMock + * @packageName angular-mocks + * @description + * + * The `ngMock` module provides support to inject and mock AngularJS services into unit tests. + * In addition, ngMock also extends various core AngularJS services such that they can be + * inspected and controlled in a synchronous manner within test code. + * + * @installation + * + * First, download the file: + * * [Google CDN](https://developers.google.com/speed/libraries/devguide#angularjs) e.g. + * `"//ajax.googleapis.com/ajax/libs/angularjs/X.Y.Z/angular-mocks.js"` + * * [NPM](https://www.npmjs.com/) e.g. `npm install angular-mocks@X.Y.Z` + * * [Yarn](https://yarnpkg.com) e.g. `yarn add angular-mocks@X.Y.Z` + * * [Bower](http://bower.io) e.g. `bower install angular-mocks#X.Y.Z` + * * [code.angularjs.org](https://code.angularjs.org/) (discouraged for production use) e.g. + * `"//code.angularjs.org/X.Y.Z/angular-mocks.js"` + * + * where X.Y.Z is the AngularJS version you are running. + * + * Then, configure your test runner to load `angular-mocks.js` after `angular.js`. + * This example uses Karma: + * + * ``` + * config.set({ + * files: [ + * 'build/angular.js', // and other module files you need + * 'build/angular-mocks.js', + * '', + * '' + * ] + * }); + * ``` + * + * Including the `angular-mocks.js` file automatically adds the `ngMock` module, so your tests + * are ready to go! + */ +angular.module('ngMock', ['ng']).provider({ + $browser: angular.mock.$BrowserProvider, + $exceptionHandler: angular.mock.$ExceptionHandlerProvider, + $log: angular.mock.$LogProvider, + $interval: angular.mock.$IntervalProvider, + $rootElement: angular.mock.$RootElementProvider, + $componentController: angular.mock.$ComponentControllerProvider +}).config(['$provide', '$compileProvider', function($provide, $compileProvider) { + $provide.decorator('$timeout', angular.mock.$TimeoutDecorator); + $provide.decorator('$$rAF', angular.mock.$RAFDecorator); + $provide.decorator('$rootScope', angular.mock.$RootScopeDecorator); + $provide.decorator('$controller', createControllerDecorator($compileProvider)); + $provide.decorator('$httpBackend', angular.mock.$httpBackendDecorator); +}]).info({ angularVersion: '"NG_VERSION_FULL"' }); + +/** + * @ngdoc module + * @name ngMockE2E + * @module ngMockE2E + * @packageName angular-mocks + * @description + * + * The `ngMockE2E` is an AngularJS module which contains mocks suitable for end-to-end testing. + * Currently there is only one mock present in this module - + * the {@link ngMockE2E.$httpBackend e2e $httpBackend} mock. + */ +angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) { + $provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator); +}]).info({ angularVersion: '"NG_VERSION_FULL"' }); + +/** + * @ngdoc service + * @name $httpBackend + * @module ngMockE2E + * @description + * Fake HTTP backend implementation suitable for end-to-end testing or backend-less development of + * applications that use the {@link ng.$http $http service}. + * + *
+ * **Note**: For fake http backend implementation suitable for unit testing please see + * {@link ngMock.$httpBackend unit-testing $httpBackend mock}. + *
+ * + * This implementation can be used to respond with static or dynamic responses via the `when` api + * and its shortcuts (`whenGET`, `whenPOST`, etc) and optionally pass through requests to the + * real $httpBackend for specific requests (e.g. to interact with certain remote apis or to fetch + * templates from a webserver). + * + * As opposed to unit-testing, in an end-to-end testing scenario or in scenario when an application + * is being developed with the real backend api replaced with a mock, it is often desirable for + * certain category of requests to bypass the mock and issue a real http request (e.g. to fetch + * templates or static files from the webserver). To configure the backend with this behavior + * use the `passThrough` request handler of `when` instead of `respond`. + * + * Additionally, we don't want to manually have to flush mocked out requests like we do during unit + * testing. For this reason the e2e $httpBackend flushes mocked out requests + * automatically, closely simulating the behavior of the XMLHttpRequest object. + * + * To setup the application to run with this http backend, you have to create a module that depends + * on the `ngMockE2E` and your application modules and defines the fake backend: + * + * ```js + * var myAppDev = angular.module('myAppDev', ['myApp', 'ngMockE2E']); + * myAppDev.run(function($httpBackend) { + * var phones = [{name: 'phone1'}, {name: 'phone2'}]; + * + * // returns the current list of phones + * $httpBackend.whenGET('/phones').respond(phones); + * + * // adds a new phone to the phones array + * $httpBackend.whenPOST('/phones').respond(function(method, url, data) { + * var phone = angular.fromJson(data); + * phones.push(phone); + * return [200, phone, {}]; + * }); + * $httpBackend.whenGET(/^\/templates\//).passThrough(); // Requests for templates are handled by the real server + * //... + * }); + * ``` + * + * Afterwards, bootstrap your app with this new module. + * + * @example + * + * + * var myApp = angular.module('myApp', []); + * + * myApp.controller('MainCtrl', function MainCtrl($http) { + * var ctrl = this; + * + * ctrl.phones = []; + * ctrl.newPhone = { + * name: '' + * }; + * + * ctrl.getPhones = function() { + * $http.get('/phones').then(function(response) { + * ctrl.phones = response.data; + * }); + * }; + * + * ctrl.addPhone = function(phone) { + * $http.post('/phones', phone).then(function() { + * ctrl.newPhone = {name: ''}; + * return ctrl.getPhones(); + * }); + * }; + * + * ctrl.getPhones(); + * }); + * + * + * var myAppDev = angular.module('myAppE2E', ['myApp', 'ngMockE2E']); + * + * myAppDev.run(function($httpBackend) { + * var phones = [{name: 'phone1'}, {name: 'phone2'}]; + * + * // returns the current list of phones + * $httpBackend.whenGET('/phones').respond(phones); + * + * // adds a new phone to the phones array + * $httpBackend.whenPOST('/phones').respond(function(method, url, data) { + * var phone = angular.fromJson(data); + * phones.push(phone); + * return [200, phone, {}]; + * }); + * }); + * + * + *
+ *
+ * + * + *
+ *

Phones

+ *
    + *
  • {{phone.name}}
  • + *
+ *
+ *
+ *
+ * + * + */ + +/** + * @ngdoc method + * @name $httpBackend#when + * @module ngMockE2E + * @description + * Creates a new backend definition. + * + * @param {string} method HTTP method. + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives + * data string and returns true if the data is as expected. + * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header + * object and returns true if the headers match the current definition. + * @param {(Array)=} keys Array of keys to assign to regex matches in request url described on + * {@link ngMock.$httpBackend $httpBackend mock}. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. + * + * - respond – + * ``` + * { function([status,] data[, headers, statusText]) + * | function(function(method, url, data, headers, params)} + * ``` + * – The respond method takes a set of static data to be returned or a function that can return + * an array containing response status (number), response data (Array|Object|string), response + * headers (Object), and the text for the status (string). + * - passThrough – `{function()}` – Any request matching a backend definition with + * `passThrough` handler will be passed through to the real backend (an XHR request will be made + * to the server.) + * - Both methods return the `requestHandler` object for possible overrides. + */ + +/** + * @ngdoc method + * @name $httpBackend#whenGET + * @module ngMockE2E + * @description + * Creates a new backend definition for GET requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(Object|function(Object))=} headers HTTP headers. + * @param {(Array)=} keys Array of keys to assign to regex matches in request url described on + * {@link ngMock.$httpBackend $httpBackend mock}. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. + */ + +/** + * @ngdoc method + * @name $httpBackend#whenHEAD + * @module ngMockE2E + * @description + * Creates a new backend definition for HEAD requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(Object|function(Object))=} headers HTTP headers. + * @param {(Array)=} keys Array of keys to assign to regex matches in request url described on + * {@link ngMock.$httpBackend $httpBackend mock}. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. + */ + +/** + * @ngdoc method + * @name $httpBackend#whenDELETE + * @module ngMockE2E + * @description + * Creates a new backend definition for DELETE requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(Object|function(Object))=} headers HTTP headers. + * @param {(Array)=} keys Array of keys to assign to regex matches in request url described on + * {@link ngMock.$httpBackend $httpBackend mock}. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. + */ + +/** + * @ngdoc method + * @name $httpBackend#whenPOST + * @module ngMockE2E + * @description + * Creates a new backend definition for POST requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives + * data string and returns true if the data is as expected. + * @param {(Object|function(Object))=} headers HTTP headers. + * @param {(Array)=} keys Array of keys to assign to regex matches in request url described on + * {@link ngMock.$httpBackend $httpBackend mock}. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. + */ + +/** + * @ngdoc method + * @name $httpBackend#whenPUT + * @module ngMockE2E + * @description + * Creates a new backend definition for PUT requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives + * data string and returns true if the data is as expected. + * @param {(Object|function(Object))=} headers HTTP headers. + * @param {(Array)=} keys Array of keys to assign to regex matches in request url described on + * {@link ngMock.$httpBackend $httpBackend mock}. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. + */ + +/** + * @ngdoc method + * @name $httpBackend#whenPATCH + * @module ngMockE2E + * @description + * Creates a new backend definition for PATCH requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives + * data string and returns true if the data is as expected. + * @param {(Object|function(Object))=} headers HTTP headers. + * @param {(Array)=} keys Array of keys to assign to regex matches in request url described on + * {@link ngMock.$httpBackend $httpBackend mock}. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. + */ + +/** + * @ngdoc method + * @name $httpBackend#whenJSONP + * @module ngMockE2E + * @description + * Creates a new backend definition for JSONP requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(Array)=} keys Array of keys to assign to regex matches in request url described on + * {@link ngMock.$httpBackend $httpBackend mock}. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. + */ +/** + * @ngdoc method + * @name $httpBackend#whenRoute + * @module ngMockE2E + * @description + * Creates a new backend definition that compares only with the requested route. + * + * @param {string} method HTTP method. + * @param {string} url HTTP url string that supports colon param matching. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. + */ +angular.mock.e2e = {}; +angular.mock.e2e.$httpBackendDecorator = + ['$rootScope', '$timeout', '$delegate', '$browser', createHttpBackendMock]; + + +/** + * @ngdoc type + * @name $rootScope.Scope + * @module ngMock + * @description + * {@link ng.$rootScope.Scope Scope} type decorated with helper methods useful for testing. These + * methods are automatically available on any {@link ng.$rootScope.Scope Scope} instance when + * `ngMock` module is loaded. + * + * In addition to all the regular `Scope` methods, the following helper methods are available: + */ +angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) { + + var $rootScopePrototype = Object.getPrototypeOf($delegate); + + $rootScopePrototype.$countChildScopes = countChildScopes; + $rootScopePrototype.$countWatchers = countWatchers; + + return $delegate; + + // ------------------------------------------------------------------------------------------ // + + /** + * @ngdoc method + * @name $rootScope.Scope#$countChildScopes + * @module ngMock + * @this $rootScope.Scope + * @description + * Counts all the direct and indirect child scopes of the current scope. + * + * The current scope is excluded from the count. The count includes all isolate child scopes. + * + * @returns {number} Total number of child scopes. + */ + function countChildScopes() { + var count = 0; // exclude the current scope + var pendingChildHeads = [this.$$childHead]; + var currentScope; + + while (pendingChildHeads.length) { + currentScope = pendingChildHeads.shift(); + + while (currentScope) { + count += 1; + pendingChildHeads.push(currentScope.$$childHead); + currentScope = currentScope.$$nextSibling; + } + } + + return count; + } + + + /** + * @ngdoc method + * @name $rootScope.Scope#$countWatchers + * @this $rootScope.Scope + * @module ngMock + * @description + * Counts all the watchers of direct and indirect child scopes of the current scope. + * + * The watchers of the current scope are included in the count and so are all the watchers of + * isolate child scopes. + * + * @returns {number} Total number of watchers. + */ + function countWatchers() { + var count = this.$$watchers ? this.$$watchers.length : 0; // include the current scope + var pendingChildHeads = [this.$$childHead]; + var currentScope; + + while (pendingChildHeads.length) { + currentScope = pendingChildHeads.shift(); + + while (currentScope) { + count += currentScope.$$watchers ? currentScope.$$watchers.length : 0; + pendingChildHeads.push(currentScope.$$childHead); + currentScope = currentScope.$$nextSibling; + } + } + + return count; + } +}]; + + +(function(jasmineOrMocha) { + + if (!jasmineOrMocha) { + return; + } + + var currentSpec = null, + injectorState = new InjectorState(), + annotatedFunctions = [], + wasInjectorCreated = function() { + return !!currentSpec; + }; + + angular.mock.$$annotate = angular.injector.$$annotate; + angular.injector.$$annotate = function(fn) { + if (typeof fn === 'function' && !fn.$inject) { + annotatedFunctions.push(fn); + } + return angular.mock.$$annotate.apply(this, arguments); + }; + + /** + * @ngdoc function + * @name angular.mock.module + * @description + * + * *NOTE*: This function is also published on window for easy access.
+ * *NOTE*: This function is declared ONLY WHEN running tests with jasmine or mocha + * + * This function registers a module configuration code. It collects the configuration information + * which will be used when the injector is created by {@link angular.mock.inject inject}. + * + * See {@link angular.mock.inject inject} for usage example + * + * @param {...(string|Function|Object)} fns any number of modules which are represented as string + * aliases or as anonymous module initialization functions. The modules are used to + * configure the injector. The 'ng' and 'ngMock' modules are automatically loaded. If an + * object literal is passed each key-value pair will be registered on the module via + * {@link auto.$provide $provide}.value, the key being the string name (or token) to associate + * with the value on the injector. + */ + var module = window.module = angular.mock.module = function() { + var moduleFns = Array.prototype.slice.call(arguments, 0); + return wasInjectorCreated() ? workFn() : workFn; + ///////////////////// + function workFn() { + if (currentSpec.$injector) { + throw new Error('Injector already created, can not register a module!'); + } else { + var fn, modules = currentSpec.$modules || (currentSpec.$modules = []); + angular.forEach(moduleFns, function(module) { + if (angular.isObject(module) && !angular.isArray(module)) { + fn = ['$provide', function($provide) { + angular.forEach(module, function(value, key) { + $provide.value(key, value); + }); + }]; + } else { + fn = module; + } + if (currentSpec.$providerInjector) { + currentSpec.$providerInjector.invoke(fn); + } else { + modules.push(fn); + } + }); + } + } + }; + + module.$$beforeAllHook = (window.before || window.beforeAll); + module.$$afterAllHook = (window.after || window.afterAll); + + // purely for testing ngMock itself + module.$$currentSpec = function(to) { + if (arguments.length === 0) return to; + currentSpec = to; + }; + + /** + * @ngdoc function + * @name angular.mock.module.sharedInjector + * @description + * + * *NOTE*: This function is declared ONLY WHEN running tests with jasmine or mocha + * + * This function ensures a single injector will be used for all tests in a given describe context. + * This contrasts with the default behaviour where a new injector is created per test case. + * + * Use sharedInjector when you want to take advantage of Jasmine's `beforeAll()`, or mocha's + * `before()` methods. Call `module.sharedInjector()` before you setup any other hooks that + * will create (i.e call `module()`) or use (i.e call `inject()`) the injector. + * + * You cannot call `sharedInjector()` from within a context already using `sharedInjector()`. + * + * ## Example + * + * Typically beforeAll is used to make many assertions about a single operation. This can + * cut down test run-time as the test setup doesn't need to be re-run, and enabling focussed + * tests each with a single assertion. + * + * ```js + * describe("Deep Thought", function() { + * + * module.sharedInjector(); + * + * beforeAll(module("UltimateQuestion")); + * + * beforeAll(inject(function(DeepThought) { + * expect(DeepThought.answer).toBeUndefined(); + * DeepThought.generateAnswer(); + * })); + * + * it("has calculated the answer correctly", inject(function(DeepThought) { + * // Because of sharedInjector, we have access to the instance of the DeepThought service + * // that was provided to the beforeAll() hook. Therefore we can test the generated answer + * expect(DeepThought.answer).toBe(42); + * })); + * + * it("has calculated the answer within the expected time", inject(function(DeepThought) { + * expect(DeepThought.runTimeMillennia).toBeLessThan(8000); + * })); + * + * it("has double checked the answer", inject(function(DeepThought) { + * expect(DeepThought.absolutelySureItIsTheRightAnswer).toBe(true); + * })); + * + * }); + * + * ``` + */ + module.sharedInjector = function() { + if (!(module.$$beforeAllHook && module.$$afterAllHook)) { + throw Error('sharedInjector() cannot be used unless your test runner defines beforeAll/afterAll'); + } + + var initialized = false; + + module.$$beforeAllHook(/** @this */ function() { + if (injectorState.shared) { + injectorState.sharedError = Error('sharedInjector() cannot be called inside a context that has already called sharedInjector()'); + throw injectorState.sharedError; + } + initialized = true; + currentSpec = this; + injectorState.shared = true; + }); + + module.$$afterAllHook(function() { + if (initialized) { + injectorState = new InjectorState(); + module.$$cleanup(); + } else { + injectorState.sharedError = null; + } + }); + }; + + module.$$beforeEach = function() { + if (injectorState.shared && currentSpec && currentSpec !== this) { + var state = currentSpec; + currentSpec = this; + angular.forEach(['$injector','$modules','$providerInjector', '$injectorStrict'], function(k) { + currentSpec[k] = state[k]; + state[k] = null; + }); + } else { + currentSpec = this; + originalRootElement = null; + annotatedFunctions = []; + } + }; + + module.$$afterEach = function() { + if (injectorState.cleanupAfterEach()) { + module.$$cleanup(); + } + }; + + module.$$cleanup = function() { + var injector = currentSpec.$injector; + + annotatedFunctions.forEach(function(fn) { + delete fn.$inject; + }); + + currentSpec.$injector = null; + currentSpec.$modules = null; + currentSpec.$providerInjector = null; + currentSpec = null; + + if (injector) { + // Ensure `$rootElement` is instantiated, before checking `originalRootElement` + var $rootElement = injector.get('$rootElement'); + var rootNode = $rootElement && $rootElement[0]; + var cleanUpNodes = !originalRootElement ? [] : [originalRootElement[0]]; + if (rootNode && (!originalRootElement || rootNode !== originalRootElement[0])) { + cleanUpNodes.push(rootNode); + } + angular.element.cleanData(cleanUpNodes); + + // Ensure `$destroy()` is available, before calling it + // (a mocked `$rootScope` might not implement it (or not even be an object at all)) + var $rootScope = injector.get('$rootScope'); + if ($rootScope && $rootScope.$destroy) $rootScope.$destroy(); + } + + // clean up jquery's fragment cache + angular.forEach(angular.element.fragments, function(val, key) { + delete angular.element.fragments[key]; + }); + + MockXhr.$$lastInstance = null; + + angular.forEach(angular.callbacks, function(val, key) { + delete angular.callbacks[key]; + }); + angular.callbacks.$$counter = 0; + }; + + (window.beforeEach || window.setup)(module.$$beforeEach); + (window.afterEach || window.teardown)(module.$$afterEach); + + /** + * @ngdoc function + * @name angular.mock.inject + * @description + * + * *NOTE*: This function is also published on window for easy access.
+ * *NOTE*: This function is declared ONLY WHEN running tests with jasmine or mocha + * + * The inject function wraps a function into an injectable function. The inject() creates new + * instance of {@link auto.$injector $injector} per test, which is then used for + * resolving references. + * + * + * ## Resolving References (Underscore Wrapping) + * Often, we would like to inject a reference once, in a `beforeEach()` block and reuse this + * in multiple `it()` clauses. To be able to do this we must assign the reference to a variable + * that is declared in the scope of the `describe()` block. Since we would, most likely, want + * the variable to have the same name of the reference we have a problem, since the parameter + * to the `inject()` function would hide the outer variable. + * + * To help with this, the injected parameters can, optionally, be enclosed with underscores. + * These are ignored by the injector when the reference name is resolved. + * + * For example, the parameter `_myService_` would be resolved as the reference `myService`. + * Since it is available in the function body as `_myService_`, we can then assign it to a variable + * defined in an outer scope. + * + * ``` + * // Defined out reference variable outside + * var myService; + * + * // Wrap the parameter in underscores + * beforeEach( inject( function(_myService_){ + * myService = _myService_; + * })); + * + * // Use myService in a series of tests. + * it('makes use of myService', function() { + * myService.doStuff(); + * }); + * + * ``` + * + * See also {@link angular.mock.module angular.mock.module} + * + * ## Example + * Example of what a typical jasmine tests looks like with the inject method. + * ```js + * + * angular.module('myApplicationModule', []) + * .value('mode', 'app') + * .value('version', 'v1.0.1'); + * + * + * describe('MyApp', function() { + * + * // You need to load modules that you want to test, + * // it loads only the "ng" module by default. + * beforeEach(module('myApplicationModule')); + * + * + * // inject() is used to inject arguments of all given functions + * it('should provide a version', inject(function(mode, version) { + * expect(version).toEqual('v1.0.1'); + * expect(mode).toEqual('app'); + * })); + * + * + * // The inject and module method can also be used inside of the it or beforeEach + * it('should override a version and test the new version is injected', function() { + * // module() takes functions or strings (module aliases) + * module(function($provide) { + * $provide.value('version', 'overridden'); // override version here + * }); + * + * inject(function(version) { + * expect(version).toEqual('overridden'); + * }); + * }); + * }); + * + * ``` + * + * @param {...Function} fns any number of functions which will be injected using the injector. + */ + + + + var ErrorAddingDeclarationLocationStack = function ErrorAddingDeclarationLocationStack(e, errorForStack) { + this.message = e.message; + this.name = e.name; + if (e.line) this.line = e.line; + if (e.sourceId) this.sourceId = e.sourceId; + if (e.stack && errorForStack) + this.stack = e.stack + '\n' + errorForStack.stack; + if (e.stackArray) this.stackArray = e.stackArray; + }; + ErrorAddingDeclarationLocationStack.prototype = Error.prototype; + + window.inject = angular.mock.inject = function() { + var blockFns = Array.prototype.slice.call(arguments, 0); + var errorForStack = new Error('Declaration Location'); + // IE10+ and PhanthomJS do not set stack trace information, until the error is thrown + if (!errorForStack.stack) { + try { + throw errorForStack; + } catch (e) { /* empty */ } + } + return wasInjectorCreated() ? WorkFn.call(currentSpec) : WorkFn; + ///////////////////// + function WorkFn() { + var modules = currentSpec.$modules || []; + var strictDi = !!currentSpec.$injectorStrict; + modules.unshift(['$injector', function($injector) { + currentSpec.$providerInjector = $injector; + }]); + modules.unshift('ngMock'); + modules.unshift('ng'); + var injector = currentSpec.$injector; + if (!injector) { + if (strictDi) { + // If strictDi is enabled, annotate the providerInjector blocks + angular.forEach(modules, function(moduleFn) { + if (typeof moduleFn === 'function') { + angular.injector.$$annotate(moduleFn); + } + }); + } + injector = currentSpec.$injector = angular.injector(modules, strictDi); + currentSpec.$injectorStrict = strictDi; + } + for (var i = 0, ii = blockFns.length; i < ii; i++) { + if (currentSpec.$injectorStrict) { + // If the injector is strict / strictDi, and the spec wants to inject using automatic + // annotation, then annotate the function here. + injector.annotate(blockFns[i]); + } + try { + injector.invoke(blockFns[i] || angular.noop, this); + } catch (e) { + if (e.stack && errorForStack) { + throw new ErrorAddingDeclarationLocationStack(e, errorForStack); + } + throw e; + } finally { + errorForStack = null; + } + } + } + }; + + + angular.mock.inject.strictDi = function(value) { + value = arguments.length ? !!value : true; + return wasInjectorCreated() ? workFn() : workFn; + + function workFn() { + if (value !== currentSpec.$injectorStrict) { + if (currentSpec.$injector) { + throw new Error('Injector already created, can not modify strict annotations'); + } else { + currentSpec.$injectorStrict = value; + } + } + } + }; + + function InjectorState() { + this.shared = false; + this.sharedError = null; + + this.cleanupAfterEach = function() { + return !this.shared || this.sharedError; + }; + } +})(window.jasmine || window.mocha); diff --git a/services/web/test/unit_frontend/coffee/bootstrap.coffee b/services/web/test/unit_frontend/coffee/bootstrap.coffee new file mode 100644 index 0000000000..52adf087f3 --- /dev/null +++ b/services/web/test/unit_frontend/coffee/bootstrap.coffee @@ -0,0 +1,4 @@ +# Stub out some globals +window.sharelatex = { + sixpackDomain: '' +} diff --git a/services/web/test/unit_frontend/coffee/ide/editor/directives/cmEditorTests.coffee b/services/web/test/unit_frontend/coffee/ide/editor/directives/cmEditorTests.coffee new file mode 100644 index 0000000000..3f74268ad9 --- /dev/null +++ b/services/web/test/unit_frontend/coffee/ide/editor/directives/cmEditorTests.coffee @@ -0,0 +1,53 @@ +define ['ide/editor/directives/cmEditor'], () -> + describe 'cmEditor', () -> + beforeEach(module('SharelatexApp')) + + beforeEach () -> + @richTextInit = sinon.stub() + window.Frontend = { + richText: { + init: @richTextInit + } + } + + it 'inits Rich Text', () -> + inject ($compile, $rootScope) -> + $compile('
')($rootScope) + expect(@richTextInit).to.have.been.called + + it 'attaches to CM', () -> + inject ($compile, $rootScope) -> + setValue = sinon.stub() + @richTextInit.returns({ setValue: setValue }) + getSnapshot = sinon.stub() + detachFromCM = sinon.stub() + attachToCM = sinon.stub() + $rootScope.sharejsDoc = { + getSnapshot: getSnapshot + detachFromCM: detachFromCM + attachToCM: attachToCM + } + + $compile('
')($rootScope) + $rootScope.$digest() + + expect(getSnapshot).to.have.been.called + expect(setValue).to.have.been.called + expect(detachFromCM).to.have.been.called + expect(attachToCM).to.have.been.called + + it 'detaches from CM when destroyed', () -> + inject ($compile, $rootScope) -> + @richTextInit.returns({ setValue: sinon.stub() }) + detachFromCM = sinon.stub() + $rootScope.sharejsDoc = { + getSnapshot: sinon.stub() + detachFromCM: detachFromCM + attachToCM: sinon.stub() + } + + $compile('
')($rootScope) + $rootScope.$digest() + $rootScope.$broadcast('destroy') + + expect(detachFromCM).to.have.been.called diff --git a/services/web/test/unit_frontend/coffee/ide/history/HistoryManagerV2Tests.coffee b/services/web/test/unit_frontend/coffee/ide/history/HistoryManagerV2Tests.coffee deleted file mode 100644 index d5e4101744..0000000000 --- a/services/web/test/unit_frontend/coffee/ide/history/HistoryManagerV2Tests.coffee +++ /dev/null @@ -1,164 +0,0 @@ -Path = require 'path' -SandboxedModule = require "sandboxed-module" -modulePath = Path.join __dirname, '../../../../../public/js/ide/history/HistoryV2Manager' -sinon = require("sinon") -expect = require("chai").expect - -describe "HistoryV2Manager", -> - beforeEach -> - @moment = {} - @ColorManager = {} - SandboxedModule.require modulePath, globals: - "define": (dependencies, builder) => - @HistoryV2Manager = builder(@moment, @ColorManager) - - @scope = - $watch: sinon.stub() - $on: sinon.stub() - @ide = {} - - @historyManager = new @HistoryV2Manager(@ide, @scope) - - it "should setup the history scope on intialization", -> - expect(@scope.history).to.deep.equal({ - isV2: true - updates: [] - nextBeforeTimestamp: null - atEnd: false - selection: { - updates: [] - pathname: null - docs: {} - range: { - fromV: null - toV: null - } - } - diff: null - }) - - describe "_perDocSummaryOfUpdates", -> - it "should return the range of updates for the docs", -> - result = @historyManager._perDocSummaryOfUpdates([{ - pathnames: ["main.tex"] - fromV: 7, toV: 9 - },{ - pathnames: ["main.tex", "foo.tex"] - fromV: 4, toV: 6 - },{ - pathnames: ["main.tex"] - fromV: 3, toV: 3 - },{ - pathnames: ["foo.tex"] - fromV: 0, toV: 2 - }]) - - expect(result).to.deep.equal({ - "main.tex": { fromV: 3, toV: 9 }, - "foo.tex": { fromV: 0, toV: 6 } - }) - - it "should track renames", -> - result = @historyManager._perDocSummaryOfUpdates([{ - pathnames: ["main2.tex"] - fromV: 5, toV: 9 - },{ - project_ops: [{ - rename: { - pathname: "main1.tex", - newPathname: "main2.tex" - } - }], - fromV: 4, toV: 4 - },{ - pathnames: ["main1.tex"] - fromV: 3, toV: 3 - },{ - project_ops: [{ - rename: { - pathname: "main0.tex", - newPathname: "main1.tex" - } - }], - fromV: 2, toV: 2 - },{ - pathnames: ["main0.tex"] - fromV: 0, toV: 1 - }]) - - expect(result).to.deep.equal({ - "main0.tex": { fromV: 0, toV: 9 } - }) - - it "should track single renames", -> - result = @historyManager._perDocSummaryOfUpdates([{ - project_ops: [{ - rename: { - pathname: "main1.tex", - newPathname: "main2.tex" - } - }], - fromV: 4, toV: 5 - }]) - - expect(result).to.deep.equal({ - "main1.tex": { fromV: 4, toV: 5 } - }) - - it "should track additions", -> - result = @historyManager._perDocSummaryOfUpdates([{ - project_ops: [{ - add: - pathname: "main.tex" - }] - fromV: 0, toV: 1 - }, { - pathnames: ["main.tex"] - fromV: 1, toV: 4 - }]) - - expect(result).to.deep.equal({ - "main.tex": { fromV: 0, toV: 4 } - }) - - it "should track single additions", -> - result = @historyManager._perDocSummaryOfUpdates([{ - project_ops: [{ - add: - pathname: "main.tex" - }] - fromV: 0, toV: 1 - }]) - - expect(result).to.deep.equal({ - "main.tex": { fromV: 0, toV: 1 } - }) - - it "should track deletions", -> - result = @historyManager._perDocSummaryOfUpdates([{ - pathnames: ["main.tex"] - fromV: 0, toV: 1 - }, { - project_ops: [{ - remove: - pathname: "main.tex" - }] - fromV: 1, toV: 2 - }]) - - expect(result).to.deep.equal({ - "main.tex": { fromV: 0, toV: 2, deleted: true } - }) - - it "should track single deletions", -> - result = @historyManager._perDocSummaryOfUpdates([{ - project_ops: [{ - remove: - pathname: "main.tex" - }] - fromV: 0, toV: 1 - }]) - - expect(result).to.deep.equal({ - "main.tex": { fromV: 0, toV: 1, deleted: true } - }) diff --git a/services/web/test/unit_frontend/coffee/ide/history/HistoryV2ManagerTests.coffee b/services/web/test/unit_frontend/coffee/ide/history/HistoryV2ManagerTests.coffee new file mode 100644 index 0000000000..358268310e --- /dev/null +++ b/services/web/test/unit_frontend/coffee/ide/history/HistoryV2ManagerTests.coffee @@ -0,0 +1,152 @@ +define ['ide/history/HistoryV2Manager'], (HistoryV2Manager) -> + describe "HistoryV2Manager", -> + beforeEach -> + @scope = + $watch: sinon.stub() + $on: sinon.stub() + @ide = {} + @historyManager = new HistoryV2Manager(@ide, @scope) + + it "should setup the history scope on intialization", -> + expect(@scope.history).to.deep.equal({ + isV2: true + updates: [] + nextBeforeTimestamp: null + atEnd: false + selection: { + updates: [] + pathname: null + docs: {} + range: { + fromV: null + toV: null + } + } + diff: null + }) + + describe "_perDocSummaryOfUpdates", -> + it "should return the range of updates for the docs", -> + result = @historyManager._perDocSummaryOfUpdates([{ + pathnames: ["main.tex"] + fromV: 7, toV: 9 + },{ + pathnames: ["main.tex", "foo.tex"] + fromV: 4, toV: 6 + },{ + pathnames: ["main.tex"] + fromV: 3, toV: 3 + },{ + pathnames: ["foo.tex"] + fromV: 0, toV: 2 + }]) + + expect(result).to.deep.equal({ + "main.tex": { fromV: 3, toV: 9 }, + "foo.tex": { fromV: 0, toV: 6 } + }) + + it "should track renames", -> + result = @historyManager._perDocSummaryOfUpdates([{ + pathnames: ["main2.tex"] + fromV: 5, toV: 9 + },{ + project_ops: [{ + rename: { + pathname: "main1.tex", + newPathname: "main2.tex" + } + }], + fromV: 4, toV: 4 + },{ + pathnames: ["main1.tex"] + fromV: 3, toV: 3 + },{ + project_ops: [{ + rename: { + pathname: "main0.tex", + newPathname: "main1.tex" + } + }], + fromV: 2, toV: 2 + },{ + pathnames: ["main0.tex"] + fromV: 0, toV: 1 + }]) + + expect(result).to.deep.equal({ + "main0.tex": { fromV: 0, toV: 9 } + }) + + it "should track single renames", -> + result = @historyManager._perDocSummaryOfUpdates([{ + project_ops: [{ + rename: { + pathname: "main1.tex", + newPathname: "main2.tex" + } + }], + fromV: 4, toV: 5 + }]) + + expect(result).to.deep.equal({ + "main1.tex": { fromV: 4, toV: 5 } + }) + + it "should track additions", -> + result = @historyManager._perDocSummaryOfUpdates([{ + project_ops: [{ + add: + pathname: "main.tex" + }] + fromV: 0, toV: 1 + }, { + pathnames: ["main.tex"] + fromV: 1, toV: 4 + }]) + + expect(result).to.deep.equal({ + "main.tex": { fromV: 0, toV: 4 } + }) + + it "should track single additions", -> + result = @historyManager._perDocSummaryOfUpdates([{ + project_ops: [{ + add: + pathname: "main.tex" + }] + fromV: 0, toV: 1 + }]) + + expect(result).to.deep.equal({ + "main.tex": { fromV: 0, toV: 1 } + }) + + it "should track deletions", -> + result = @historyManager._perDocSummaryOfUpdates([{ + pathnames: ["main.tex"] + fromV: 0, toV: 1 + }, { + project_ops: [{ + remove: + pathname: "main.tex" + }] + fromV: 1, toV: 2 + }]) + + expect(result).to.deep.equal({ + "main.tex": { fromV: 0, toV: 2, deleted: true } + }) + + it "should track single deletions", -> + result = @historyManager._perDocSummaryOfUpdates([{ + project_ops: [{ + remove: + pathname: "main.tex" + }] + fromV: 0, toV: 1 + }]) + + expect(result).to.deep.equal({ + "main.tex": { fromV: 0, toV: 1, deleted: true } + }) diff --git a/services/web/test/unit_frontend/coffee/ide/history/displayUserNameTests.coffee b/services/web/test/unit_frontend/coffee/ide/history/displayUserNameTests.coffee deleted file mode 100644 index 2a756171a7..0000000000 --- a/services/web/test/unit_frontend/coffee/ide/history/displayUserNameTests.coffee +++ /dev/null @@ -1,68 +0,0 @@ -Path = require 'path' -SandboxedModule = require "sandboxed-module" -modulePath = Path.join __dirname, '../../../../../public/js/ide/history/util/displayNameForUser' -sinon = require("sinon") -expect = require("chai").expect - -describe "displayNameForUser", -> - beforeEach -> - SandboxedModule.require modulePath, globals: - "define": (dependencies, builder) => - @displayNameForUser = builder() - "window": @window = {} - @window.user = { id: 42 } - - it "should return 'Anonymous' with no user", -> - expect( - @displayNameForUser(null) - ).to.equal "Anonymous" - - it "should return 'you' when the user has the same id as the window", -> - expect( - @displayNameForUser({ - id: @window.user.id - email: "james.allen@overleaf.com" - first_name: "James" - last_name: "Allen" - }) - ).to.equal "you" - - it "should return the first_name and last_name when present", -> - expect( - @displayNameForUser({ - id: @window.user.id + 1 - email: "james.allen@overleaf.com" - first_name: "James" - last_name: "Allen" - }) - ).to.equal "James Allen" - - it "should return only the firstAname if no last_name", -> - expect( - @displayNameForUser({ - id: @window.user.id + 1 - email: "james.allen@overleaf.com" - first_name: "James" - last_name: "" - }) - ).to.equal "James" - - it "should return the email username if there are no names", -> - expect( - @displayNameForUser({ - id: @window.user.id + 1 - email: "james.allen@overleaf.com" - first_name: "" - last_name: "" - }) - ).to.equal "james.allen" - - it "should return the '?' if it has nothing", -> - expect( - @displayNameForUser({ - id: @window.user.id + 1 - email: "" - first_name: "" - last_name: "" - }) - ).to.equal "?" diff --git a/services/web/test/unit_frontend/coffee/ide/history/util/displayNameForUserTests.coffee b/services/web/test/unit_frontend/coffee/ide/history/util/displayNameForUserTests.coffee new file mode 100644 index 0000000000..32bd77f65c --- /dev/null +++ b/services/web/test/unit_frontend/coffee/ide/history/util/displayNameForUserTests.coffee @@ -0,0 +1,59 @@ +define ['ide/history/util/displayNameForUser'], (displayNameForUser) -> + describe "displayNameForUser", -> + beforeEach -> + window.user = { id: 42 } + + it "should return 'Anonymous' with no user", -> + expect( + displayNameForUser(null) + ).to.equal "Anonymous" + + it "should return 'you' when the user has the same id as the window", -> + expect( + displayNameForUser({ + id: window.user.id + email: "james.allen@overleaf.com" + first_name: "James" + last_name: "Allen" + }) + ).to.equal "you" + + it "should return the first_name and last_name when present", -> + expect( + displayNameForUser({ + id: window.user.id + 1 + email: "james.allen@overleaf.com" + first_name: "James" + last_name: "Allen" + }) + ).to.equal "James Allen" + + it "should return only the firstAname if no last_name", -> + expect( + displayNameForUser({ + id: window.user.id + 1 + email: "james.allen@overleaf.com" + first_name: "James" + last_name: "" + }) + ).to.equal "James" + + it "should return the email username if there are no names", -> + expect( + displayNameForUser({ + id: window.user.id + 1 + email: "james.allen@overleaf.com" + first_name: "" + last_name: "" + }) + ).to.equal "james.allen" + + it "should return the '?' if it has nothing", -> + expect( + displayNameForUser({ + id: window.user.id + 1 + email: "" + first_name: "" + last_name: "" + }) + ).to.equal "?" diff --git a/services/web/test/unit_frontend/coffee/test-main.coffee b/services/web/test/unit_frontend/coffee/test-main.coffee new file mode 100644 index 0000000000..e20acba4d7 --- /dev/null +++ b/services/web/test/unit_frontend/coffee/test-main.coffee @@ -0,0 +1,14 @@ +# Set up requirejs to load the tests +# Uses heuristic that test filenames end with Tests.js +tests = [] +for file of window.__karma__.files + if window.__karma__.files.hasOwnProperty(file) + if /Tests\.js$/.test(file) + tests.push(file) + +requirejs.config + baseUrl: '/base/public/js' + paths: + "moment": "libs/moment-2.9.0" + deps: tests + callback: window.__karma__.start