diff --git a/Gulpfile.js b/Gulpfile.js
index 46ba42f..44f2f0c 100644
--- a/Gulpfile.js
+++ b/Gulpfile.js
@@ -1,18 +1,20 @@
/**
* Dev dependencies
*/
-
-var gulp = require('gulp');
-var connect = require('gulp-connect');
-var gutil = require('gulp-util');
-var uglify = require('gulp-uglify');
-var eslint = require('gulp-eslint');
-var sass = require('gulp-sass');
-var sourcemaps = require('gulp-sourcemaps');
-var minifyCSS = require('gulp-minify-css');
-var autoprefixer = require('gulp-autoprefixer');
-var concat = require('gulp-concat');
-
+var gulp = require('gulp'),
+ connect = require('gulp-connect'),
+ gutil = require('gulp-util'),
+ uglify = require('gulp-uglify'),
+ sass = require('gulp-sass'),
+ sourcemaps = require('gulp-sourcemaps'),
+ minifyCSS = require('gulp-minify-css'),
+ autoprefixer = require('gulp-autoprefixer'),
+ concat = require('gulp-concat'),
+ deleteLines = require('gulp-delete-lines'),
+ Server = require('karma').Server,
+ fpath = require('path'),
+ child_process = require('child_process'),
+ eslint = require('gulp-eslint');
/**
* File paths
*
@@ -44,6 +46,10 @@ var path = {
jsAngular: {
src: basePath.src + '/' + jsAngularDir + '/',
dest: basePath.dest + '/' + jsAngularDir + '/'
+ },
+ e2e:{
+ src:'./tests/integration/',
+ dest:'./tests'
}
};
var jsModuleFile = path.jsAngular.src + 'core/app-setup/app.module.js';
@@ -56,8 +62,10 @@ var jsModuleFile = path.jsAngular.src + 'core/app-setup/app.module.js';
var files = {};
+files.e2e = [path.e2e.src];
+files.specs= [path.jsAngular.src + '**/*.spec.js','!'+path.jsAngular.src+'reStart-app.spec.js'];
files.scssSrc = [path.css.src + '**/*.scss'];
-files.jsUserSrcAngular = [path.jsAngular.src + '**/*.js', '!' + path.jsAngular.src + jsAngularScript];
+files.jsUserSrcAngular = [path.jsAngular.src + '**/*.js', '!' + path.jsAngular.src + jsAngularScript , '!'+path.jsAngular.src + '**/*.spec.js'];
files.jsUserSrcAssets = [path.js.src + '**/*.js', '!' + path.js.src + jsUserScript, '!' + path.js.src + 'vendor/*'];
files.jsUserSrcAll = files.jsUserSrcAngular.concat(files.jsUserSrcAssets);
files.jsVendorSrc = [path.jsVendor.src + 'jquery.js', path.jsVendor.src + 'angular.js', path.jsVendor.src + '**/*.js', '!' + path.jsVendor.src + 'modernizr.min.js', '!' + path.jsVendor.src + 'vendor.js'];
@@ -174,6 +182,12 @@ function jsVendor() {
*/
function jsAngular() {
return gulp.src([jsModuleFile].concat(files.jsUserSrcAngular))
+ //remove lines marked with //test code if production
+ .pipe(isProduction ?deleteLines({
+ 'filters': [
+ /test code/
+ ]
+ }): gutil.noop())
.pipe(sourcemaps.init())
.pipe(concat(jsAngularScript))
.pipe(sourcemaps.write())
@@ -181,6 +195,38 @@ function jsAngular() {
.pipe(gulp.dest(path.jsAngular.dest));
}
+/**
+ * function tests()
+ *
+ * Init sourcemaps
+ * Concatenate .spec files
+ * Write sourcemaps
+ * Save
+ */
+function tests() {
+ return gulp.src(files.specs)
+ .pipe(sourcemaps.init())
+ .pipe(concat('reStart-app.spec.js'))
+ .pipe(sourcemaps.write())
+ .pipe(gulp.dest(path.jsAngular.dest));
+}
+
+/**
+ * function e2e()
+ *
+ * Init sourcemaps
+ * Concatenate .spec files
+ * Write sourcemaps
+ * Save
+ */
+function e2e() {
+ return gulp.src(files.e2e)
+ .pipe(sourcemaps.init())
+ .pipe(concat('e2e.js'))
+ .pipe(sourcemaps.write())
+ .pipe(gulp.dest(path.e2e.dest));
+}
+
/**
* function serve()
*
@@ -200,6 +246,41 @@ function serve() {
}
/**
+ * function karma()
+ *
+ * Start karma test runner
+ * Use singleRun false for CI testing
+ */
+ function karma(){
+ return gulp.task('karma', function (done) {
+ new Server({
+ configFile: __dirname + '/karma.conf.js',
+ singleRun: true
+ }, done).start();
+ });
+ }
+
+/**
+ * function e2eTests
+ *
+ * Start protractor and runs e2e tests
+ */
+ function e2eTests(){
+ var argv = process.argv.slice(3); // forward args to protractor
+ return child_process.spawn(getProtractorBinary('protractor'), argv, {
+ stdio: 'inherit'
+ })//.once('close', done);
+ }
+ function getProtractorBinary(binaryName){
+ var winExt = /^win/.test(process.platform)? '.cmd' : '';
+ var pkgPath = require.resolve('protractor');
+ var protractorDir = fpath.resolve(fpath.join(fpath.dirname(pkgPath), '..', 'bin'));
+ return fpath.join(protractorDir, '/'+binaryName+winExt);
+}
+
+
+/**
+
* Default build task
*
* If not production, watch for file changes and execute the appropriate task
@@ -226,8 +307,13 @@ function defaultTask() {
// compile JS Angular files
gulp.watch(files.jsUserSrcAngular, ['jsAngular']);
+
+ //compile jasmine unit tests
+ gulp.watch(files.specs, ['tests']);
+
+ //compile protractor tests
+ gulp.watch(files.e2e, ['e2e']);
}
-
/**
* Gulp tasks
*/
@@ -237,6 +323,21 @@ gulp.task('jsValidate', jsValidate);
gulp.task('jsUser', jsUser);
gulp.task('jsVendor', jsVendor);
gulp.task('jsAngular', jsAngular);
+gulp.task('tests', tests);
+gulp.task('e2e', e2e);
gulp.task('serve', serve);
-gulp.task('js', ['jsVendor', 'jsValidate', 'jsUser', 'jsAngular']);
-gulp.task('default', ['serve', 'styles', 'js'], defaultTask);
\ No newline at end of file
+//Start karma after files have been rebuilt and test compiled
+gulp.task('karma',['jsUser','jsVendor','jsAngular','tests'],karma);
+//Start protractor after karma runs
+gulp.task('protractor',['e2e','serve'],e2eTests);
+
+/**
+ * Default build task
+ *
+ * If not production, watch for file changes and execute the appropriate task
+ *
+ * Use "gulp --prod" to trigger production/build mode from commandline
+ */
+
+gulp.task('js',['jsVendor', 'jsValidate', 'jsUser', 'jsAngular']);
+gulp.task('default',['serve', 'styles', 'js'], defaultTask);
\ No newline at end of file
diff --git a/karma.conf.js b/karma.conf.js
new file mode 100644
index 0000000..c95f313
--- /dev/null
+++ b/karma.conf.js
@@ -0,0 +1,79 @@
+// Karma configuration
+// Generated on Tue Dec 01 2015 14:06:19 GMT-0500 (Eastern Standard Time)
+
+module.exports = function(config) {
+ config.set({
+
+ // base path that will be used to resolve all patterns (eg. files, exclude)
+ basePath: '',
+
+
+ // frameworks to use
+ // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
+ frameworks: ['jasmine'],
+
+
+ // list of files / patterns to load in the browser
+ files: [
+ 'src/assets/js/vendor/vendor.js',
+ 'src/assets/js/scripts.js',
+ 'src/reStart-app/reStart-app.js',
+ 'src/reStart-app/reStart-app.spec.js',
+ 'src/reStart-app/**/*.html'
+ ],
+
+
+ // list of files to exclude
+ exclude: [
+ ],
+
+
+ // preprocess matching files before serving them to the browser
+ // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
+ preprocessors: {
+ "src/reStart-app/**/*.tpl.html": ["ng-html2js"]
+ },
+
+ ngHtml2JsPreprocessor: {
+ stripPrefix:'src/',
+ // the name of the Angular module to create
+ moduleName: 'templates'
+ },
+
+ // test results reporter to use
+ // possible values: 'dots', 'progress'
+ // available reporters: https://npmjs.org/browse/keyword/karma-reporter
+ reporters: ['progress'],
+
+
+ // web server port
+ port: 9876,
+
+
+ // enable / disable colors in the output (reporters and logs)
+ colors: true,
+
+
+ // level of logging
+ // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
+ logLevel: config.LOG_INFO,
+
+
+ // enable / disable watching file and executing tests whenever any file changes
+ autoWatch: true,
+
+
+ // start these browsers
+ // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
+ browsers: ['Chrome'],
+
+
+ // Continuous Integration mode
+ // if true, Karma captures browsers, runs the tests and exits
+ singleRun: false,
+
+ // Concurrency level
+ // how many browser should be started simultanous
+ concurrency: Infinity
+ })
+}
diff --git a/package.json b/package.json
index 0396a83..f7c7b2d 100644
--- a/package.json
+++ b/package.json
@@ -32,10 +32,18 @@
"gulp-connect": "^2.2.0",
"gulp-debug": "^2.0.1",
"gulp-eslint": "^1.1.1",
+ "gulp-delete-lines": "0.0.7",
"gulp-minify-css": "^1.0.0",
+ "gulp-protractor": "^2.0.0",
"gulp-sass": "^2.0.4",
"gulp-sourcemaps": "^1.5.1",
"gulp-uglify": "^1.1.0",
- "gulp-util": "^3.0.4"
+ "gulp-util": "^3.0.4",
+ "jasmine-core": "^2.3.4",
+ "karma": "^0.13.15",
+ "karma-chrome-launcher": "^0.2.2",
+ "karma-jasmine": "^0.3.6",
+ "karma-ng-html2js-preprocessor": "^0.2.0",
+ "protractor": "^3.0.0"
}
}
diff --git a/protractor.conf.js b/protractor.conf.js
new file mode 100644
index 0000000..8b9192d
--- /dev/null
+++ b/protractor.conf.js
@@ -0,0 +1,7 @@
+exports.config = {
+ framework: 'jasmine',
+ //version must match jar file
+ seleniumServerJar: './node_modules/protractor/selenium/selenium-server-standalone-2.48.2.jar',
+ //seleniumAddress: 'http://localhost:4444/wd/hub',
+ specs: ['./tests/e2e.js']
+};
\ No newline at end of file
diff --git a/src/assets/css/styles.css b/src/assets/css/styles.css
index 1b28c61..c541bc6 100644
--- a/src/assets/css/styles.css
+++ b/src/assets/css/styles.css
@@ -178,8 +178,8 @@ tr {
/*-- Inline link touch targets --*/
.touch p a {
- margin: 0 -0.5em;
- padding: 0 0.5em; }
+ margin: 0 -.5em;
+ padding: 0 .5em; }
/*-- Forms --*/
input[type="text"],
@@ -210,7 +210,7 @@ button {
body {
background: #eee;
color: #333;
- font: normal 16px/1.2 "Helvetica Neue", Helvetica, Arial, Verdana, sans-serif; }
+ font: normal 16px/1.2 'Helvetica Neue', Helvetica, Arial, Verdana, sans-serif; }
.layout-canvas {
background: #fff; }
@@ -315,7 +315,7 @@ body {
background: #fff; }
body {
color: #000;
- font: normal 16px/1.4 Georgia, "Times New Roman", serif; }
+ font: normal 16px/1.4 Georgia, 'Times New Roman', serif; }
/*-- Hidden Elements --*/
.header,
.footer {
@@ -382,8 +382,7 @@ body {
bottom: -9px; }
.nav-open .toggle-offcanvas span {
background: transparent; }
- .nav-open .toggle-offcanvas span:before,
- .nav-open .toggle-offcanvas span:after {
+ .nav-open .toggle-offcanvas span:before, .nav-open .toggle-offcanvas span:after {
top: 0; }
.nav-open .toggle-offcanvas span:before {
-webkit-transform: rotate(45deg);
@@ -415,9 +414,7 @@ body {
.header .nav-list a {
display: block;
padding: 6px; }
- .header .nav-list a:hover,
- .header .nav-list a:active,
- .header .nav-list a:focus {
+ .header .nav-list a:hover, .header .nav-list a:active, .header .nav-list a:focus {
text-decoration: none; }
@media screen and (min-width: 768px) {
.header .nav-list {
@@ -481,4 +478,4 @@ body {
404
--------------------*/
-/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInN0eWxlcy5zY3NzIiwiY29yZS9wYXJ0aWFscy9fY29sb3JzLnZhcnMuc2NzcyIsImNvcmUvcGFydGlhbHMvX2xheW91dC52YXJzLnNjc3MiLCJjb3JlL3BhcnRpYWxzL19yZXNwb25zaXZlLnBhcnRpYWwuc2NzcyIsImNvcmUvX2Jhc2Uuc2NzcyIsInN0eWxlcy5jc3MiLCJjb3JlL19mb250cy5zY3NzIiwiY29yZS9fcHJlc2VudGF0aW9uLnNjc3MiLCJjb3JlL19sYXlvdXQuc2NzcyIsImNvcmUvX3ByaW50LnNjc3MiLCJtb2R1bGVzL19oZWFkZXIuc2NzcyIsIm1vZHVsZXMvX25hdi5zY3NzIiwibW9kdWxlcy9fZm9vdGVyLnNjc3MiLCJtb2R1bGVzL19sb2FkaW5nLnNjc3MiLCJwYWdlcy9fNDA0LnNjc3MiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7OztHQU1FO0FBRUYsZUFBYztBQ1JkOzt1QkFFc0I7QUFFdEIsNEJBQTJCO0FBRTNCLG9EQUFtRDtBQVNuRCx5Q0FBd0M7QUFLeEMsNEJBQTJCO0FDcEIzQjs7dUJBRXNCO0FBRXRCLGtCQUFpQjtBQ0pqQjs7dUJBRXNCO0FBRXRCLG9CQUFtQjtBQUtuQixpQkFBZ0I7QUNUaEI7O3VCQUVzQjtBQVFhOzs7Ozs7O0VBQ2xDLFdBQVU7RUFBRSxZQUFXO0VBQ3ZCLFdBQVU7RUFDVixrQkFBaUI7RUFBRSxjQUFhO0VBQ2hDLHNCQUFxQjtFQUFFLHFCQUFvQjtFQUFFLHNCQUFxQjtFQUNsRSxnQkFBZTtFQUNmLHVCQUFzQjtFQUN0QiwwQkFBeUIsRUFQa0I7O0FBUzVDO0VBQU8sb0JBQW1CO0VBQUUsZ0NBQStCLEVBQXJEOztBQUNGO0VBQUssa0JBQWlCLEVBQWxCOztBQUNSO0VBQVEsMkJBQTBCO0VBQUUsbUJBQWtCLEVBQS9DOztBQUNNO0VBQUssa0JBQWlCO0VBQUUscUJBQW9CLEVBQXhDOztBQUNaO0VBQU0sZ0JBQWUsRUFBaEI7O0FBQ1U7RUFBSyxxQkFBb0IsRUFBckI7O0FBQ2dFO0VBQVUsZ0JBQWUsRUFBaEI7O0FBQ3JGO0VBQ1gsd0JBQXVCLEVBREo7O0FBSXBCOzt1QkFFc0I7QUFFdEIsb0JBQW1CO0FBRW5CO0VBQUssb0JBQW1CLEVBQXBCOztBQUNKO0VBQVMsbUJBQWtCLEVBQW5COztBQUNDO0VBQUksaURBQWdELEVBQWpEOztBQUNZO0VBQUkscUJBQW9CLEVBQXJCOztBQUMzQjtFQUFLLDBCQUF5QjtFQUFFLG9CQUFtQixFQUEvQzs7QUFDSjtFQUFLLDZCQUE0QjtFQUFFLG9CQUFtQixFQUFsRDs7QUFDOEM7RUFBUyxpQkFBZ0IsRUFBakI7O0FBQzFEO0VBQU0sK0JBQThCLEVBQS9COztBQUNpQjtFQUFJLDJCQUEwQjtFQUFFLGNBQWEsRUFBMUM7O0FBQ3JCO0VBQU8sa0JBQWlCO0VBQUUsd0JBQXVCO0VBQUUsK0RBQThEO0VBQUUsYUFBWSxFQUF6SDs7QUFDSztFQUFXLHdCQUF1QjtFQUFFLHNCQUFxQjtFQUFFLG9CQUFtQjtFQUFFLGtCQUFnQixFQUF0Rjs7QUFDbEI7RUFBUyx3QkFBdUIsRUFBeEI7O0FBQ2Y7RUFBVyxnQkFBZSxFQUFoQjs7QUFBb0IsNkRBQTREO0FBQzlFO0VBQU0sZ0JBQWUsRUFBaEI7O0FBQ2pCO0VBQU0sdUJBQXNCLEVBQXZCOztBQUNMO0VBQU0scUJBQW9CLEVBQXJCOztBQUNMO0VBQVEsZ0JBQWU7RUFBRSxjQUFhLEVBQS9COztBQUNQO0VBQVEsK0JIekNZLEVHeUNiOztBQUNQO0VBQUssbUJBQWtCLEVBQW5COztBQUNJO0VBQUssbUJBQWlCLEVBQWxCOztBQUNaO0VBQUssK0JINUNlLEVHNENoQjs7QUFFSixtQkFBa0I7QUFHVDs7RUFDUixjQUFhO0VBQ2IsZ0JBQWUsRUFGQzs7QUFJUjtFQUFTLGFBQVksRUFBYjs7QUFDWjtFQUFZLFNBQVEsRUFBVDs7QUFFaEIsaUVBQWdFO0FDZ0doRTs7Ozs7O0VEeEZDLDBCQUF5QixFQURiOztBQUliLDRCQUEyQjtBQUUzQjtFQUNDLGtCQUFpQjtFQUNqQixtQkFBa0I7RUFDbEIscUJBQW9CLEVBSGhCOztBQU1MLG9DQUFtQztBQUUxQjtFQUNSLGtCQUFlO0VBQ2Ysa0JBQWUsRUFGSjs7QUFLWixnQkFBZTtBQUtmOzs7O0VBQ0MsaUJBQWdCO0VBQUUscUJBQW9CLEVBRDdCOztBQUdJO0VBQ2Isc0NIckZvQjtFR3NGcEIsNENIdEZvQixFR29GTTs7QUFJM0I7RUFDQyxvQkFBbUI7RUFDbkIsdUJBQXNCO0VBQ3RCLG9CQUFtQjtFQUNuQixrQkFBZ0I7RUFDaEIsd0JBQXVCLEVBTGhCOztBRTFHUjs7dUJBRXNCO0FBRXRCLGtDQUFpQztBQ0pqQzs7dUJBRXNCO0FBRXRCO0VBQ0Msa0JOUW1CO0VNUG5CLGFOSXFCO0VNSHJCLCtFQUE2RSxFQUh4RTs7QUFLTjtFQUNDLGtCTkRpQixFTUFGOztBQUloQix5RkFBd0Y7QUFFeEY7RUFDQyxhTFZ3QixFS1NQO0VKSGpCO0lJR0Q7TUFJRSxrQkxaNEIsRUtRWixFQUFBOztBQU9sQjtFQUNDLGlCQUFnQjtFQUNoQixzQkFBb0IsRUFGSDtFSlZqQjtJSVVEO01BS0UsZUFBYztNQUNkLG9CQUFtQixFQU5ILEVBQUE7O0FDdEJsQjs7dUJBRXNCO0FBRXRCO0VBQ0Msa0JBQWlCLEVBRFo7O0FBR047RUFDQyxrQkFBaUI7RUFBRSx1REFBc0QsRUFEeEQ7O0FBSWxCLDBHQUF5RztBQUV6RyxpQ0FBZ0M7QUFFaEM7RUFDQyxxQ0FBNEI7RUFBNUIsNkJBQTRCO0VBQzVCLG9CQUFtQjtFQUNuQixTQUFRO0VBQ1IsYUFBWSxFQUpHO0VMSGY7SUtHRDtNQU9FLDBCQUFpQjtNQUFqQixrQkFBaUI7TUFDakIseUJBQWdCO01BQWhCLHFCQUFnQjtNQUFoQixpQkFBZ0IsRUFSRixFQUFBO0VBVUU7SUFDaEIsa0RBQWdDO0lBQWhDLGtDQUFnQztJQUNoQyx5Q0FBc0I7SUFBdEIsaUNBQXNCLEVBRkg7RUFJTztJQUMxQiw2Q0FBc0I7SUFBdEIscUNBQXNCLEVBRE87RUFHQTtJQUM3QixhQUFZLEVBRG9COztBQUtsQyxpQkFBZ0I7QUx6QmY7RUsyQkQ7SUFFRSxlQUFjLEVBRkssRUFBQTs7QUFNckIscUJBQW9CO0FBRVo7RUFDUCxxQ0FBNEI7RUFBNUIsNkJBQTRCO0VBQzVCLGVBQWM7RUFBRSxxQkFBb0I7RUFDcEMsY0FBYTtFQUNiLGtCQUFpQjtFQUNqQixvQkFBbUI7RUFDbkIsUUFBTztFQUNQLGNBQWEsRUFQQTtFTG5DYjtJS21DTztNQVVOLGdCQUFlO01BQ2Ysb0JBQW1CO01BQ25CLGFBQVksRUFaQSxFQUFBO0VBZUg7O0lBQ1QsZ0JBQWU7SUFBRSxxQkFBb0IsRUFEekI7RUFHSTtJQUNoQiw2Q0FBc0I7SUFBdEIscUNBQXNCLEVBREg7SUxyRHBCO01LcURpQjtRQUlmLHlCQUFnQjtRQUFoQixxQkFBZ0I7UUFBaEIsaUJBQWdCLEVBSkUsRUFBQTtFQU9ZO0lBQy9CLGFBQVksRUFEc0I7RUFHTDtJQUM3QixjQUFhLEVBRG1CO0lML0RqQztNSytEOEI7UUFJNUIsU0FBUSxFQUp1QixFQUFBOztBQzNFbEM7O3VCQUVzQjtBQUV0QjtFQUVDO0lBQ0Msa0JBQWlCLEVBRGY7RUFHSDtJQUNDLGFBQVk7SUFDWix5REFBdUQsRUFGbEQ7RUFLTiwwQkFBeUI7RUFHekI7O0lBQ0MsZUFBYyxFQUROO0VBSVQseUJBQXdCO0VBR3ZCOztJQUNBLGFBQVk7SUFDWiw0QkFBMkIsRUFGakI7RUFLRjs7SUFDUiwrQkFBNEI7SUFDNUIsZ0JBQWUsRUFGQyxFQUFBOztBVFJsQixrQkFBaUI7QVVyQmpCOzt1QkFFc0I7QUFFdEI7RUFDQyxrQlRNcUI7RVNMckIsYVRHaUI7RVNGakIsY0FBYTtFQUNiLHFCQUFvQjtFQUNwQixvQkFBbUIsRUFMQztFQU9uQjtJQUNBLGlCQUFnQjtJQUNoQixtQkFBa0I7SUFDbEIsV0FBVTtJQUNWLHFCQUFtQjtJQUNuQixvQkFBbUI7SUFDbkIsUUFBTztJQUNQLG9CQUFtQjtJQUNuQixhQUFZLEVBUkE7O0FDWGQ7O3VCQUVzQjtBQUV0Qix1R0FBc0c7QUFFdEc7RUFDQyxrQlZJcUI7RVVIckIsa0RWU3FCO0VVUnJCLHVCQUFzQjtFQUN0QixjQUFhO0VBQ2Isc0JBQW9CO0VBQ3BCLG9CQUFtQjtFQUNuQixvQkFBbUI7RUFDbkIsYUFBWTtFQUNaLGNBQWEsRUFUSztFQWFkOzs7SUFDSCxrQlZYZ0I7SVVZaEIsb0JBQW1CO0lBQ25CLGFBQVk7SUFDWixnQkFBZTtJQUNmLGFBQVk7SUFDWixvQkFBbUI7SUFDbkIsMkNBQWlDO0lBQWpDLG1DQUFpQztJQUNqQyxhQUFZLEVBUkQ7RUFXVjtJQUNBLFdBQVUsRUFERDtFQUdUO0lBQ0EsY0FBYSxFQURMO0VBS1Q7SUFDQyx5QkFBd0IsRUFEbkI7SUFJSjs7TUFDQSxRQUFPLEVBREM7SUFHUjtNQUNBLGtDQUFpQjtNQUFqQiw4QkFBaUI7TUFBakIsMEJBQWlCLEVBRFI7SUFHVDtNQUNBLG1DQUFpQjtNQUFqQiwrQkFBaUI7TUFBakIsMkJBQWlCLEVBRFQ7O0FBT1osNEZBQTJGO0FBRW5GO0VBQ1Asc0RBQXNDO0VBQ3RDLGFUckR3QixFU21EWDtFUjdDYjtJUTZDTztNQUtOLGtCVm5Eb0I7TVVvRHBCLGtCQUFpQjtNQUNqQixrQlR6RDRCLEVTa0RoQixFQUFBO0VBVUw7SUFDUCxtQkFBa0IsRUFEVTtJUnZEN0I7TVF1RFE7UUFJTixtRFZ0RG1CLEVVa0RRLEVBQUE7RUFPNUI7SUFDQSxrQkFBaUI7SUFDakIsa0JBQWlCO0lBQ2pCLGlCQUFnQixFQUhUO0lBS1A7TUFDQyxnQkFBZTtNQUNmLGNBQWEsRUFGWDtNQU1EOzs7UUFDQSx1QkFBc0IsRUFEZDtJUnpFWDtNUThEQztRQWlCQyxzQkFBYztRQUFkLHVCQUFjO1FBQWQsc0JBQWM7UUFBZCxlQUFjO1FBQ2QseUJBQWdCO1FBQWhCLHFCQUFnQjtRQUFoQixpQkFBZ0I7UUFDaEIsMEJBQXdCO1FBQXhCLGlDQUF3QjtRQUF4Qix1QkFBd0I7UUFBeEIseUJBQXdCO1FBQ3hCLFlBQVc7UUFDWCxhQUFZLEVBckJOO1FBMEJMOzs7O1VBQ0EsYVY1RmMsRVUyRk47UUFHVDtVQUNDLGlCQUFlLEVBRFosRUFBQTs7QUN2R1A7O3VCQUVzQjtBQUV0QjtFQUNDLGFWQ3dCO0VVQXhCLG9CQUFtQixFQUZYO0VUUVI7SVNSRDtNQUtFLGtCVkY0QixFVUhyQixFQUFBOztBQ0pUOzt1QkFFc0I7QUFHcEI7RUFDQSxjQUFhO0VBQ2IsaUJBQWdCO0VBQ2YsUUFBTztFQUFFLFNBQVE7RUFDbEIsYUFBWTtFQUNaLGNBQWEsRUFMSDs7QUFPVjtFQUNBLHNDQUFnQjtFQUNoQixvQkFBbUI7RUFDbEIsUUFBTztFQUFFLFdBQVU7RUFDcEIsYUFBWSxFQUpGOztBQU1WO0VBQ0EseUJBQXVCO0VBQ3ZCLG9CQUFtQjtFQUNsQixVQUFTO0VBQUUsV0FBVSxFQUhaOztBYlVaLGdCQUFlO0FjNUJmOzt1QkFFc0IiLCJmaWxlIjoic3R5bGVzLmNzcyIsInNvdXJjZXNDb250ZW50IjpbIi8qXG4gKiByZVN0YXJ0IEJvaWxlcnBsYXRlIC8gcmVTdGFydCBBbmd1bGFyXG4gKiBBdXRob3I6IEtpbSBNYWlkYSBcbiAqIEF1dGhvciBVUkk6IDxodHRwOi8va2ltLW1haWRhLmNvbT5cbiAqIFNvdXJjZTogPGh0dHBzOi8vZ2l0aHViLmNvbS9rbWFpZGEvcmVTdGFydC1hbmd1bGFyPlxuICogTGljZW5zZTogR05VIFB1YmxpYyBMaWNlbnNlXG4qL1xuXG4vKi0tIENvcmUgLS0qL1xuXG4vLyBwYXJ0aWFsc1xuQGltcG9ydCAnY29yZS9wYXJ0aWFscy9jb2xvcnMudmFycyc7XG5AaW1wb3J0ICdjb3JlL3BhcnRpYWxzL2xheW91dC52YXJzJztcbkBpbXBvcnQgJ2NvcmUvcGFydGlhbHMvcmVzcG9uc2l2ZS5wYXJ0aWFsJztcblxuQGltcG9ydCAnY29yZS9iYXNlJztcbkBpbXBvcnQgJ2NvcmUvZm9udHMnO1xuQGltcG9ydCAnY29yZS9wcmVzZW50YXRpb24nO1xuQGltcG9ydCAnY29yZS9sYXlvdXQnO1xuQGltcG9ydCAnY29yZS9wcmludCc7XG5cbi8qLS0gTW9kdWxlcyAtLSovXG5cbkBpbXBvcnQgJ21vZHVsZXMvaGVhZGVyJztcbkBpbXBvcnQgJ21vZHVsZXMvbmF2JztcbkBpbXBvcnQgJ21vZHVsZXMvZm9vdGVyJztcbkBpbXBvcnQgJ21vZHVsZXMvbG9hZGluZyc7XG5cbi8qLS0gUGFnZXMgLS0qL1xuXG5AaW1wb3J0ICdwYWdlcy80MDQnOyIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cblx0Q09MT1IgVkFSSUFCTEVTXG4tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5cbi8qLS0gQ29sb3IgZGVmaW5pdGlvbnMgLS0qL1xuXG4vKiBIZXggY29kZSBuYW1lcyBmcm9tIGh0dHA6Ly9uYW1lLW9mLWNvbG9yLmNvbS8gKi9cblxuJGNvbG9yLWJsYWNrOiAjMDAwO1xuJGNvbG9yLXdoaXRlOiAjZmZmO1xuJGNvbG9yLW1pbmVTaGFmdDogIzMzMztcbiRjb2xvci1kYXZ5c0dyYXk6ICM1NTU7XG4kY29sb3ItY2VsZXN0ZTogI2NjYztcbiRjb2xvci1nYWxsZXJ5OiAjZWVlO1xuXG4vKiBDb2xvciBwYXJ0aWFsbHkgZGVmaW5lZCBieSBvcGFjaXR5ICovXG5cbiRjb2xvci1ncmF5LXJnYmE6IHJnYmEoMjU1LDI1NSwyNTUsLjUpO1xuJGNvbG9yLXJlZC1yZ2JhOiByZ2JhKDE2OSw2OCw2NiwuNik7XG5cbi8qLS0gQ29sb3IgYnkgZnVuY3Rpb24gLS0qL1xuXG4kY29sb3ItYmctbGlnaHQ6ICRjb2xvci1nYWxsZXJ5O1xuJGNvbG9yLWJnOiAkY29sb3Itd2hpdGU7XG4kY29sb3ItYm9yZGVyOiAkY29sb3ItY2VsZXN0ZTtcbiRjb2xvci1tb2R1bGUtYmc6ICRjb2xvci1jZWxlc3RlO1xuJGNvbG9yLW5hdi1iZzogJGNvbG9yLWRhdnlzR3JheTtcbiRjb2xvci10ZXh0OiAkY29sb3ItbWluZVNoYWZ0O1xuJGNvbG9yLWxpbmstY29udHJhc3Q6ICRjb2xvci13aGl0ZTtcbiRjb2xvci1kaXNhYmxlZC1yZ2JhOiAkY29sb3ItZ3JheS1yZ2JhO1xuJGNvbG9yLWludmFsaWQtcmdiYTogJGNvbG9yLXJlZC1yZ2JhOyIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cblx0TEFZT1VUIFZBUklBQkxFU1xuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuXG4vKi0tIFBhZGRpbmcgLS0qL1xuXG4kcGFkZGluZy1zY3JlZW4tc21hbGw6IDMlO1xuJHBhZGRpbmctc2NyZWVuLWxhcmdlOiAxLjUlIDMlOyIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cblx0ICBSRVNQT05TSVZFXG4tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5cbi8qLS0gVmFyaWFibGVzIC0tKi9cblxuJG1xLXNtYWxsOiAnc2NyZWVuIGFuZCAobWF4LXdpZHRoOiA3NjdweCknO1xuJG1xLWxhcmdlOiAnc2NyZWVuIGFuZCAobWluLXdpZHRoOiA3NjhweCknO1xuXG4vKi0tIE1peGlucyAtLSovXG5cbkBtaXhpbiBtcSgkbXFTdHJpbmcpIHtcblx0QG1lZGlhICN7JG1xU3RyaW5nfSB7XG5cdFx0QGNvbnRlbnQ7XG5cdH1cbn0iLCIvKi0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cdCAgQ1NTIFJFU0VUXG4tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5cbmh0bWwsIGJvZHksIGRpdiwgc3BhbiwgaDEsIGgyLCBoMywgaDQsIGg1LCBoNiwgcCwgYmxvY2txdW90ZSwgcHJlLFxuYSwgYWJiciwgYWNyb255bSwgYWRkcmVzcywgYmlnLCBjaXRlLCBjb2RlLCBkZWwsIGRmbiwgZW0sIGltZywgaW5zLFxua2JkLCBxLCBzLCBzYW1wLCBzbWFsbCwgc3Ryb25nLCB0dCwgdmFyLCBkbCwgZHQsXG5kZCwgb2wsIHVsLCBsaSwgZmllbGRzZXQsIGZvcm0sIGlucHV0LCBidXR0b24sIHRleHRhcmVhLCBsYWJlbCwgbGVnZW5kLCB0YWJsZSwgY2FwdGlvbixcbnRib2R5LCB0Zm9vdCwgdGhlYWQsIHRyLCB0aCwgdGQsXG5hcnRpY2xlLCBhc2lkZSwgY2FudmFzLCBkZXRhaWxzLCBmaWdjYXB0aW9uLCBmaWd1cmUsXG5mb290ZXIsIGhlYWRlciwgaGdyb3VwLCBtZW51LCBuYXYsIHNlY3Rpb24ge1xuXHRtYXJnaW46IDA7IHBhZGRpbmc6IDA7XG5cdGJvcmRlcjogMDtcblx0Ym9yZGVyLXJhZGl1czogMDtcdC8qIGZvciBpT1MgKi9cblx0Zm9udC13ZWlnaHQ6IGluaGVyaXQ7IGZvbnQtc3R5bGU6IGluaGVyaXQ7IGZvbnQtZmFtaWx5OiBpbmhlcml0O1xuXHRsaW5lLWhlaWdodDogMTtcblx0dGV4dC1kZWNvcmF0aW9uOiBub25lO1xuXHR2ZXJ0aWNhbC1hbGlnbjogYmFzZWxpbmU7XG59XG5odG1sIHsgb3ZlcmZsb3cteTogc2Nyb2xsOyAtd2Via2l0LXRleHQtc2l6ZS1hZGp1c3Q6IDEwMCU7IH1cbm9sLCB1bCB7IGxpc3Qtc3R5bGU6IG5vbmU7IH1cbnRhYmxlIHsgYm9yZGVyLWNvbGxhcHNlOiBjb2xsYXBzZTsgYm9yZGVyLXNwYWNpbmc6IDA7IH1cbmNhcHRpb24sIHRoLCB0ZCB7IHRleHQtYWxpZ246IGxlZnQ7IGZvbnQtd2VpZ2h0OiBub3JtYWw7IH1cbnN1cCwgc3ViIHsgbGluZS1oZWlnaHQ6IDE7IH1cbmgxLCBoMiwgaDMsIGg0LCBoNSwgaDYgeyBmb250LXdlaWdodDogbm9ybWFsOyB9XG5hcnRpY2xlLCBhc2lkZSwgY2FudmFzLCBkZXRhaWxzLCBmaWdjYXB0aW9uLCBmaWd1cmUsIGZvb3RlciwgaGVhZGVyLCBoZ3JvdXAsIG1lbnUsIG5hdiwgc2VjdGlvbiB7IGRpc3BsYXk6IGJsb2NrOyB9XG4qLCo6YmVmb3JlLCo6YWZ0ZXIge1xuXHRib3gtc2l6aW5nOiBib3JkZXItYm94O1xufVxuXG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cdCAgIEJBU0lDU1xuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuXG4vKi0tIE5vcm1hbGl6ZSAtLSovXG5cbmVtIHsgZm9udC1zdHlsZTogaXRhbGljOyB9XG5zdHJvbmcgeyBmb250LXdlaWdodDogYm9sZDsgfVxucTpsYW5nKGVuKSB7IHF1b3RlczogJyYjODIyMDsnICcmIzgyMjE7JyAnJiM4MjE2OycgJyYjODIxNzsnOyB9XG5oMSwgaDIsIGgzLCBoNCwgaDUsIGg2LCBwIHsgbWFyZ2luLWJvdHRvbTogMTVweDsgfVxudWwgeyBsaXN0LXN0eWxlOiBvdXRzaWRlIGRpc2M7IHBhZGRpbmctbGVmdDogMjRweDsgfVxub2wgeyBsaXN0LXN0eWxlOiBvdXRzaWRlIGRlY2ltYWw7IHBhZGRpbmctbGVmdDogMjRweDsgfVxuYSwgbGFiZWwsIGlucHV0W3R5cGU9YnV0dG9uXSwgaW5wdXRbdHlwZT1zdWJtaXRdLCBidXR0b24geyBjdXJzb3I6IHBvaW50ZXI7IH1cbmRlbCB7IHRleHQtZGVjb3JhdGlvbjogbGluZS10aHJvdWdoOyB9XG5hYmJyW3RpdGxlXSwgZGZuW3RpdGxlXSB7IGJvcmRlci1ib3R0b206IDFweCBkb3R0ZWQ7IGN1cnNvcjogaGVscDsgfVxucHJlLGNvZGUgeyBiYWNrZ3JvdW5kOiAjZWVlOyBib3JkZXI6IDFweCBzb2xpZCAjY2NjOyBmb250LWZhbWlseTogQ29uc29sYXMsICdMdWNpZGEgQ29uc29sZScsICdDb3VyaWVyIE5ldycsIHNlcmlmOyBwYWRkaW5nOiAyJTsgfVxuaW5wdXQsIHNlbGVjdCwgdGV4dGFyZWEgeyBib3JkZXI6IDFweCBzb2xpZCAjY2NjOyBmb250LWZhbWlseTogaW5oZXJpdDsgZm9udC1zaXplOiBpbmhlcml0OyBwYWRkaW5nOiAzcHggNnB4OyB9XG5pbnB1dCwgc2VsZWN0IHsgdmVydGljYWwtYWxpZ246IG1pZGRsZTsgfVxudGV4dGFyZWEgeyBvdmVyZmxvdzogYXV0bzsgfSAvKiBwcmV2ZW50cyBzY3JvbGxiYXIgZnJvbSBzaG93aW5nIHVwIHdoZW4gdW5uZWVkZWQgaW4gSUUgKi9cbnNtYWxsLCBzdXAsIHN1YiB7IGZvbnQtc2l6ZTogODUlOyB9XG5zdXAgeyB2ZXJ0aWNhbC1hbGlnbjogc3VwZXI7IH1cbnN1YiB7IHZlcnRpY2FsLWFsaWduOiBzdWI7IH1cbnRhYmxlIHsgbWFyZ2luOiAxMHB4IDA7IHBhZGRpbmc6IDNweDsgfVxudGhlYWQgeyBib3JkZXItYm90dG9tOiAycHggc29saWQgJGNvbG9yLWJvcmRlcjsgfVxudGggeyBmb250LXdlaWdodDogYm9sZDsgfVxudGgsIHRyLCB0ZCB7IHBhZGRpbmc6IDRweCAxMnB4OyB9XG50ciB7IGJvcmRlci1ib3R0b206IDFweCBzb2xpZCAkY29sb3ItYm9yZGVyfVxuXG4vKi0tIENsZWFyZml4IC0tKi9cblxuLmNsZWFyZml4OmJlZm9yZSxcbi5jbGVhcmZpeDphZnRlciB7XG5cdGNvbnRlbnQ6IFwiIFwiO1xuXHRkaXNwbGF5OiB0YWJsZTtcbn1cbi5jbGVhcmZpeDphZnRlciB7IGNsZWFyOiBib3RoOyB9XG4uaWU3IC5jbGVhcmZpeCB7IHpvb206IDE7IH1cblxuLyotLSBuZy1jbG9hazogcHJldmVudCBGT1VDIGJlZm9yZSBBbmd1bGFyIEphdmFTY3JpcHQgbG9hZHMgLS0qL1xuXG5bbmdcXDpjbG9ha10sXG5bbmctY2xvYWtdLFxuW2RhdGEtbmctY2xvYWtdLFxuW3gtbmctY2xvYWtdLFxuLm5nLWNsb2FrLFxuLngtbmctY2xvYWsge1xuXHRkaXNwbGF5OiBub25lICFpbXBvcnRhbnQ7XG59XG5cbi8qLS0gSW1hZ2UgUmVwbGFjZW1lbnQgLS0qL1xuXG4uaXIge1xuXHRvdmVyZmxvdzogaGlkZGVuO1xuXHR0ZXh0LWluZGVudDogMjAwJTtcblx0d2hpdGUtc3BhY2U6IG5vd3JhcDtcbn1cblxuLyotLSBJbmxpbmUgbGluayB0b3VjaCB0YXJnZXRzIC0tKi9cblxuLnRvdWNoIHAgYSB7XG5cdG1hcmdpbjogMCAtLjVlbTtcblx0cGFkZGluZzogMCAuNWVtO1xufVxuXG4vKi0tIEZvcm1zIC0tKi9cblxuaW5wdXRbdHlwZT1cInRleHRcIl0sXG5pbnB1dFt0eXBlPVwibnVtYmVyXCJdLFxuaW5wdXRbdHlwZT1cInBhc3N3b3JkXCJdLFxudGV4dGFyZWEge1xuXHRmb250LXNpemU6IDE2cHg7XHQvKiBmb3IgaU9TIHBob25lcyAqL1xufVxuaW5wdXQubmctZGlydHkubmctaW52YWxpZCB7XG5cdGJvcmRlci1jb2xvcjogJGNvbG9yLWludmFsaWQtcmdiYTtcblx0Ym94LXNoYWRvdzogMCAwIDZweCAkY29sb3ItaW52YWxpZC1yZ2JhO1xufVxuYnV0dG9uIHtcblx0Ym9yZGVyLXJhZGl1czogNHB4O1xuXHRkaXNwbGF5OiBpbmxpbmUtYmxvY2s7XG5cdGZvbnQtc2l6ZTogaW5oZXJpdDtcblx0cGFkZGluZzogNXB4IDRweDtcblx0dmVydGljYWwtYWxpZ246IG1pZGRsZTtcbn0iLCIvKlxuICogcmVTdGFydCBCb2lsZXJwbGF0ZSAvIHJlU3RhcnQgQW5ndWxhclxuICogQXV0aG9yOiBLaW0gTWFpZGEgXG4gKiBBdXRob3IgVVJJOiA8aHR0cDovL2tpbS1tYWlkYS5jb20+XG4gKiBTb3VyY2U6IDxodHRwczovL2dpdGh1Yi5jb20va21haWRhL3JlU3RhcnQtYW5ndWxhcj5cbiAqIExpY2Vuc2U6IEdOVSBQdWJsaWMgTGljZW5zZVxuKi9cbi8qLS0gQ29yZSAtLSovXG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cdENPTE9SIFZBUklBQkxFU1xuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuLyotLSBDb2xvciBkZWZpbml0aW9ucyAtLSovXG4vKiBIZXggY29kZSBuYW1lcyBmcm9tIGh0dHA6Ly9uYW1lLW9mLWNvbG9yLmNvbS8gKi9cbi8qIENvbG9yIHBhcnRpYWxseSBkZWZpbmVkIGJ5IG9wYWNpdHkgKi9cbi8qLS0gQ29sb3IgYnkgZnVuY3Rpb24gLS0qL1xuLyotLS0tLS0tLS0tLS0tLS0tLS0tLVxuXHRMQVlPVVQgVkFSSUFCTEVTXG4tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG4vKi0tIFBhZGRpbmcgLS0qL1xuLyotLS0tLS0tLS0tLS0tLS0tLS0tLVxuXHQgIFJFU1BPTlNJVkVcbi0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbi8qLS0gVmFyaWFibGVzIC0tKi9cbi8qLS0gTWl4aW5zIC0tKi9cbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cblx0ICBDU1MgUkVTRVRcbi0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbmh0bWwsIGJvZHksIGRpdiwgc3BhbiwgaDEsIGgyLCBoMywgaDQsIGg1LCBoNiwgcCwgYmxvY2txdW90ZSwgcHJlLFxuYSwgYWJiciwgYWNyb255bSwgYWRkcmVzcywgYmlnLCBjaXRlLCBjb2RlLCBkZWwsIGRmbiwgZW0sIGltZywgaW5zLFxua2JkLCBxLCBzLCBzYW1wLCBzbWFsbCwgc3Ryb25nLCB0dCwgdmFyLCBkbCwgZHQsXG5kZCwgb2wsIHVsLCBsaSwgZmllbGRzZXQsIGZvcm0sIGlucHV0LCBidXR0b24sIHRleHRhcmVhLCBsYWJlbCwgbGVnZW5kLCB0YWJsZSwgY2FwdGlvbixcbnRib2R5LCB0Zm9vdCwgdGhlYWQsIHRyLCB0aCwgdGQsXG5hcnRpY2xlLCBhc2lkZSwgY2FudmFzLCBkZXRhaWxzLCBmaWdjYXB0aW9uLCBmaWd1cmUsXG5mb290ZXIsIGhlYWRlciwgaGdyb3VwLCBtZW51LCBuYXYsIHNlY3Rpb24ge1xuICBtYXJnaW46IDA7XG4gIHBhZGRpbmc6IDA7XG4gIGJvcmRlcjogMDtcbiAgYm9yZGVyLXJhZGl1czogMDtcbiAgLyogZm9yIGlPUyAqL1xuICBmb250LXdlaWdodDogaW5oZXJpdDtcbiAgZm9udC1zdHlsZTogaW5oZXJpdDtcbiAgZm9udC1mYW1pbHk6IGluaGVyaXQ7XG4gIGxpbmUtaGVpZ2h0OiAxO1xuICB0ZXh0LWRlY29yYXRpb246IG5vbmU7XG4gIHZlcnRpY2FsLWFsaWduOiBiYXNlbGluZTsgfVxuXG5odG1sIHtcbiAgb3ZlcmZsb3cteTogc2Nyb2xsO1xuICAtd2Via2l0LXRleHQtc2l6ZS1hZGp1c3Q6IDEwMCU7IH1cblxub2wsIHVsIHtcbiAgbGlzdC1zdHlsZTogbm9uZTsgfVxuXG50YWJsZSB7XG4gIGJvcmRlci1jb2xsYXBzZTogY29sbGFwc2U7XG4gIGJvcmRlci1zcGFjaW5nOiAwOyB9XG5cbmNhcHRpb24sIHRoLCB0ZCB7XG4gIHRleHQtYWxpZ246IGxlZnQ7XG4gIGZvbnQtd2VpZ2h0OiBub3JtYWw7IH1cblxuc3VwLCBzdWIge1xuICBsaW5lLWhlaWdodDogMTsgfVxuXG5oMSwgaDIsIGgzLCBoNCwgaDUsIGg2IHtcbiAgZm9udC13ZWlnaHQ6IG5vcm1hbDsgfVxuXG5hcnRpY2xlLCBhc2lkZSwgY2FudmFzLCBkZXRhaWxzLCBmaWdjYXB0aW9uLCBmaWd1cmUsIGZvb3RlciwgaGVhZGVyLCBoZ3JvdXAsIG1lbnUsIG5hdiwgc2VjdGlvbiB7XG4gIGRpc3BsYXk6IGJsb2NrOyB9XG5cbiosICo6YmVmb3JlLCAqOmFmdGVyIHtcbiAgYm94LXNpemluZzogYm9yZGVyLWJveDsgfVxuXG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cdCAgIEJBU0lDU1xuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuLyotLSBOb3JtYWxpemUgLS0qL1xuZW0ge1xuICBmb250LXN0eWxlOiBpdGFsaWM7IH1cblxuc3Ryb25nIHtcbiAgZm9udC13ZWlnaHQ6IGJvbGQ7IH1cblxucTpsYW5nKGVuKSB7XG4gIHF1b3RlczogJyYjODIyMDsnICcmIzgyMjE7JyAnJiM4MjE2OycgJyYjODIxNzsnOyB9XG5cbmgxLCBoMiwgaDMsIGg0LCBoNSwgaDYsIHAge1xuICBtYXJnaW4tYm90dG9tOiAxNXB4OyB9XG5cbnVsIHtcbiAgbGlzdC1zdHlsZTogb3V0c2lkZSBkaXNjO1xuICBwYWRkaW5nLWxlZnQ6IDI0cHg7IH1cblxub2wge1xuICBsaXN0LXN0eWxlOiBvdXRzaWRlIGRlY2ltYWw7XG4gIHBhZGRpbmctbGVmdDogMjRweDsgfVxuXG5hLCBsYWJlbCwgaW5wdXRbdHlwZT1idXR0b25dLCBpbnB1dFt0eXBlPXN1Ym1pdF0sIGJ1dHRvbiB7XG4gIGN1cnNvcjogcG9pbnRlcjsgfVxuXG5kZWwge1xuICB0ZXh0LWRlY29yYXRpb246IGxpbmUtdGhyb3VnaDsgfVxuXG5hYmJyW3RpdGxlXSwgZGZuW3RpdGxlXSB7XG4gIGJvcmRlci1ib3R0b206IDFweCBkb3R0ZWQ7XG4gIGN1cnNvcjogaGVscDsgfVxuXG5wcmUsIGNvZGUge1xuICBiYWNrZ3JvdW5kOiAjZWVlO1xuICBib3JkZXI6IDFweCBzb2xpZCAjY2NjO1xuICBmb250LWZhbWlseTogQ29uc29sYXMsICdMdWNpZGEgQ29uc29sZScsICdDb3VyaWVyIE5ldycsIHNlcmlmO1xuICBwYWRkaW5nOiAyJTsgfVxuXG5pbnB1dCwgc2VsZWN0LCB0ZXh0YXJlYSB7XG4gIGJvcmRlcjogMXB4IHNvbGlkICNjY2M7XG4gIGZvbnQtZmFtaWx5OiBpbmhlcml0O1xuICBmb250LXNpemU6IGluaGVyaXQ7XG4gIHBhZGRpbmc6IDNweCA2cHg7IH1cblxuaW5wdXQsIHNlbGVjdCB7XG4gIHZlcnRpY2FsLWFsaWduOiBtaWRkbGU7IH1cblxudGV4dGFyZWEge1xuICBvdmVyZmxvdzogYXV0bzsgfVxuXG4vKiBwcmV2ZW50cyBzY3JvbGxiYXIgZnJvbSBzaG93aW5nIHVwIHdoZW4gdW5uZWVkZWQgaW4gSUUgKi9cbnNtYWxsLCBzdXAsIHN1YiB7XG4gIGZvbnQtc2l6ZTogODUlOyB9XG5cbnN1cCB7XG4gIHZlcnRpY2FsLWFsaWduOiBzdXBlcjsgfVxuXG5zdWIge1xuICB2ZXJ0aWNhbC1hbGlnbjogc3ViOyB9XG5cbnRhYmxlIHtcbiAgbWFyZ2luOiAxMHB4IDA7XG4gIHBhZGRpbmc6IDNweDsgfVxuXG50aGVhZCB7XG4gIGJvcmRlci1ib3R0b206IDJweCBzb2xpZCAjY2NjOyB9XG5cbnRoIHtcbiAgZm9udC13ZWlnaHQ6IGJvbGQ7IH1cblxudGgsIHRyLCB0ZCB7XG4gIHBhZGRpbmc6IDRweCAxMnB4OyB9XG5cbnRyIHtcbiAgYm9yZGVyLWJvdHRvbTogMXB4IHNvbGlkICNjY2M7IH1cblxuLyotLSBDbGVhcmZpeCAtLSovXG4uY2xlYXJmaXg6YmVmb3JlLFxuLmNsZWFyZml4OmFmdGVyIHtcbiAgY29udGVudDogXCIgXCI7XG4gIGRpc3BsYXk6IHRhYmxlOyB9XG5cbi5jbGVhcmZpeDphZnRlciB7XG4gIGNsZWFyOiBib3RoOyB9XG5cbi5pZTcgLmNsZWFyZml4IHtcbiAgem9vbTogMTsgfVxuXG4vKi0tIG5nLWNsb2FrOiBwcmV2ZW50IEZPVUMgYmVmb3JlIEFuZ3VsYXIgSmF2YVNjcmlwdCBsb2FkcyAtLSovXG5bbmdcXDpjbG9ha10sXG5bbmctY2xvYWtdLFxuW2RhdGEtbmctY2xvYWtdLFxuW3gtbmctY2xvYWtdLFxuLm5nLWNsb2FrLFxuLngtbmctY2xvYWsge1xuICBkaXNwbGF5OiBub25lICFpbXBvcnRhbnQ7IH1cblxuLyotLSBJbWFnZSBSZXBsYWNlbWVudCAtLSovXG4uaXIge1xuICBvdmVyZmxvdzogaGlkZGVuO1xuICB0ZXh0LWluZGVudDogMjAwJTtcbiAgd2hpdGUtc3BhY2U6IG5vd3JhcDsgfVxuXG4vKi0tIElubGluZSBsaW5rIHRvdWNoIHRhcmdldHMgLS0qL1xuLnRvdWNoIHAgYSB7XG4gIG1hcmdpbjogMCAtMC41ZW07XG4gIHBhZGRpbmc6IDAgMC41ZW07IH1cblxuLyotLSBGb3JtcyAtLSovXG5pbnB1dFt0eXBlPVwidGV4dFwiXSxcbmlucHV0W3R5cGU9XCJudW1iZXJcIl0sXG5pbnB1dFt0eXBlPVwicGFzc3dvcmRcIl0sXG50ZXh0YXJlYSB7XG4gIGZvbnQtc2l6ZTogMTZweDtcbiAgLyogZm9yIGlPUyBwaG9uZXMgKi8gfVxuXG5pbnB1dC5uZy1kaXJ0eS5uZy1pbnZhbGlkIHtcbiAgYm9yZGVyLWNvbG9yOiByZ2JhKDE2OSwgNjgsIDY2LCAwLjYpO1xuICBib3gtc2hhZG93OiAwIDAgNnB4IHJnYmEoMTY5LCA2OCwgNjYsIDAuNik7IH1cblxuYnV0dG9uIHtcbiAgYm9yZGVyLXJhZGl1czogNHB4O1xuICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7XG4gIGZvbnQtc2l6ZTogaW5oZXJpdDtcbiAgcGFkZGluZzogNXB4IDRweDtcbiAgdmVydGljYWwtYWxpZ246IG1pZGRsZTsgfVxuXG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cdCAgIEZPTlRTXG4tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG4vKiBGdWxsIEBmb250LWZhY2UgZGVjbGFyYXRpb24gKi9cbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cblx0IFBSRVNFTlRBVElPTlxuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuYm9keSB7XG4gIGJhY2tncm91bmQ6ICNlZWU7XG4gIGNvbG9yOiAjMzMzO1xuICBmb250OiBub3JtYWwgMTZweC8xLjIgXCJIZWx2ZXRpY2EgTmV1ZVwiLCBIZWx2ZXRpY2EsIEFyaWFsLCBWZXJkYW5hLCBzYW5zLXNlcmlmOyB9XG5cbi5sYXlvdXQtY2FudmFzIHtcbiAgYmFja2dyb3VuZDogI2ZmZjsgfVxuXG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gQ29udGVudCAtLSovXG4uY29udGVudC13cmFwcGVyIHtcbiAgcGFkZGluZzogMyU7IH1cbiAgQG1lZGlhIHNjcmVlbiBhbmQgKG1pbi13aWR0aDogNzY4cHgpIHtcbiAgICAuY29udGVudC13cmFwcGVyIHtcbiAgICAgIHBhZGRpbmc6IDEuNSUgMyU7IH0gfVxuXG4uY29udGVudC1oZWFkaW5nIHtcbiAgZm9udC1zaXplOiAzNnB4O1xuICBtYXJnaW46IC0xLjUlIDAgMjBweDsgfVxuICBAbWVkaWEgc2NyZWVuIGFuZCAobWluLXdpZHRoOiA3NjhweCkge1xuICAgIC5jb250ZW50LWhlYWRpbmcge1xuICAgICAgbWFyZ2luLXRvcDogMDtcbiAgICAgIHRleHQtYWxpZ246IGNlbnRlcjsgfSB9XG5cbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiBMQVlPVVQgRlVOQ1RJT05BTElUWVxuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuYm9keSB7XG4gIG1pbi13aWR0aDogMzIwcHg7IH1cblxuLmxheW91dC1vdmVyZmxvdyB7XG4gIG92ZXJmbG93OiBoaWRkZW47XG4gIC8qIG5lY2Vzc2FyeSB0byBoYW5kbGUgb2ZmY2FudmFzIHNjcm9sbGJhciBiZWhhdmlvciAqLyB9XG5cbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSBPZmYtY2FudmFzIEZ1bmN0aW9uYWxpdHkgLS0qL1xuLyotLSBDb250ZW50IGNhbnZhcyB3cmFwcGVyIC0tKi9cbi5sYXlvdXQtY2FudmFzIHtcbiAgYmFja2ZhY2UtdmlzaWJpbGl0eTogaGlkZGVuO1xuICBwb3NpdGlvbjogcmVsYXRpdmU7XG4gIGxlZnQ6IDA7XG4gIHdpZHRoOiAxMDAlOyB9XG4gIEBtZWRpYSBzY3JlZW4gYW5kIChtaW4td2lkdGg6IDc2OHB4KSB7XG4gICAgLmxheW91dC1jYW52YXMge1xuICAgICAgdHJhbnNpdGlvbjogbm9uZTtcbiAgICAgIHRyYW5zZm9ybTogbm9uZTsgfSB9XG4gIC5jc3N0cmFuc2Zvcm1zM2QgLmxheW91dC1jYW52YXMge1xuICAgIHRyYW5zaXRpb246IHRyYW5zZm9ybSAyNTBtcyBlYXNlO1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlM2QoMCwgMCwgMCk7IH1cbiAgLmNzc3RyYW5zZm9ybXMzZCAubmF2LW9wZW4gLmxheW91dC1jYW52YXMge1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlM2QoMjcwcHgsIDAsIDApOyB9XG4gIC5uby1jc3N0cmFuc2Zvcm1zM2QgLm5hdi1vcGVuIC5sYXlvdXQtY2FudmFzIHtcbiAgICBsZWZ0OiAyNzBweDsgfVxuXG4vKi0tIEhlYWRlciAtLSovXG5AbWVkaWEgc2NyZWVuIGFuZCAobWluLXdpZHRoOiA3NjhweCkge1xuICAuaGVhZGVyLW1vYmlsZS1wYWdlIHtcbiAgICBkaXNwbGF5OiBub25lOyB9IH1cblxuLyotLSBOYXZpZ2F0aW9uIC0tKi9cbi5oZWFkZXIgLm5hdiB7XG4gIGJhY2tmYWNlLXZpc2liaWxpdHk6IGhpZGRlbjtcbiAgZGlzcGxheTogbm9uZTtcbiAgLyogZGVhbCB3aXRoIEZPVUMgKi9cbiAgaGVpZ2h0OiAxMDAlO1xuICBvdmVyZmxvdy15OiBhdXRvO1xuICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gIHRvcDogMDtcbiAgd2lkdGg6IDI3MHB4OyB9XG4gIEBtZWRpYSBzY3JlZW4gYW5kIChtaW4td2lkdGg6IDc2OHB4KSB7XG4gICAgLmhlYWRlciAubmF2IHtcbiAgICAgIGRpc3BsYXk6IGJsb2NrO1xuICAgICAgcG9zaXRpb246IHJlbGF0aXZlO1xuICAgICAgd2lkdGg6IDEwMCU7IH0gfVxuICAubmF2LWNsb3NlZCAuaGVhZGVyIC5uYXYsXG4gIC5uYXYtb3BlbiAuaGVhZGVyIC5uYXYge1xuICAgIGRpc3BsYXk6IGJsb2NrO1xuICAgIC8qIGRlYWwgd2l0aCBGT1VDICovIH1cbiAgLmNzc3RyYW5zZm9ybXMzZCAuaGVhZGVyIC5uYXYge1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlM2QoLTEwMCUsIDAsIDApOyB9XG4gICAgQG1lZGlhIHNjcmVlbiBhbmQgKG1pbi13aWR0aDogNzY4cHgpIHtcbiAgICAgIC5jc3N0cmFuc2Zvcm1zM2QgLmhlYWRlciAubmF2IHtcbiAgICAgICAgdHJhbnNmb3JtOiBub25lOyB9IH1cbiAgLm5vLWNzc3RyYW5zZm9ybXMzZCAubmF2LWNsb3NlZCAuaGVhZGVyIC5uYXYge1xuICAgIGxlZnQ6IC0xMDAlOyB9XG4gIC5uby1jc3N0cmFuc2Zvcm1zM2QgLm5hdi1vcGVuIC5oZWFkZXIgLm5hdiB7XG4gICAgbGVmdDogLTI3MHB4OyB9XG4gICAgQG1lZGlhIHNjcmVlbiBhbmQgKG1pbi13aWR0aDogNzY4cHgpIHtcbiAgICAgIC5uby1jc3N0cmFuc2Zvcm1zM2QgLm5hdi1vcGVuIC5oZWFkZXIgLm5hdiB7XG4gICAgICAgIGxlZnQ6IDA7IH0gfVxuXG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cdCAgIFBSSU5UXG4tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5AbWVkaWEgcHJpbnQge1xuICAqIHtcbiAgICBiYWNrZ3JvdW5kOiAjZmZmOyB9XG4gIGJvZHkge1xuICAgIGNvbG9yOiAjMDAwO1xuICAgIGZvbnQ6IG5vcm1hbCAxNnB4LzEuNCBHZW9yZ2lhLCBcIlRpbWVzIE5ldyBSb21hblwiLCBzZXJpZjsgfVxuICAvKi0tIEhpZGRlbiBFbGVtZW50cyAtLSovXG4gIC5oZWFkZXIsXG4gIC5mb290ZXIge1xuICAgIGRpc3BsYXk6IG5vbmU7IH1cbiAgLyotLSBTaG93IGxpbmsgVVJMcyAtLSovXG4gIGE6bGluayxcbiAgYTp2aXNpdGVkIHtcbiAgICBjb2xvcjogYmx1ZTtcbiAgICB0ZXh0LWRlY29yYXRpb246IHVuZGVybGluZTsgfVxuICBhOmxpbms6YWZ0ZXIsXG4gIGE6dmlzaXRlZDphZnRlciB7XG4gICAgY29udGVudDogXCIgW1wiIGF0dHIoaHJlZikgXCJdIFwiO1xuICAgIGZvbnQtc2l6ZTogNzUlOyB9IH1cblxuLyotLSBNb2R1bGVzIC0tKi9cbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cblx0ICAgIEhFQURFUlxuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuLmhlYWRlci1tb2JpbGUtcGFnZSB7XG4gIGJhY2tncm91bmQ6ICM1NTU7XG4gIGNvbG9yOiAjZmZmO1xuICBoZWlnaHQ6IDUwcHg7XG4gIG1hcmdpbi1ib3R0b206IDEwcHg7XG4gIHBvc2l0aW9uOiByZWxhdGl2ZTsgfVxuICAuaGVhZGVyLW1vYmlsZS1wYWdlLXNpdGVUaXRsZSB7XG4gICAgZm9udC1zaXplOiAzMHB4O1xuICAgIGxpbmUtaGVpZ2h0OiA1MHB4O1xuICAgIG1hcmdpbjogMDtcbiAgICBwYWRkaW5nOiAwIDAgMCA1MHB4O1xuICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICB0b3A6IDA7XG4gICAgdGV4dC1hbGlnbjogY2VudGVyO1xuICAgIHdpZHRoOiAxMDAlOyB9XG5cbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cblx0IE5BVklHQVRJT05cbi0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSBIYW1idXJnZXIgbWVudSB0b2dnbGUgLS0qL1xuLnRvZ2dsZS1vZmZjYW52YXMge1xuICBiYWNrZ3JvdW5kOiAjNTU1O1xuICBib3JkZXItcmlnaHQ6IDFweCBzb2xpZCByZ2JhKDI1NSwgMjU1LCAyNTUsIDAuNSk7XG4gIGRpc3BsYXk6IGlubGluZS1ibG9jaztcbiAgaGVpZ2h0OiA1MHB4O1xuICBwYWRkaW5nOiAyMy41cHggMTNweDtcbiAgcG9zaXRpb246IHJlbGF0aXZlO1xuICB0ZXh0LWFsaWduOiBjZW50ZXI7XG4gIHdpZHRoOiA1MHB4O1xuICB6LWluZGV4OiAxMDA7IH1cbiAgLnRvZ2dsZS1vZmZjYW52YXMgc3BhbixcbiAgLnRvZ2dsZS1vZmZjYW52YXMgc3BhbjpiZWZvcmUsXG4gIC50b2dnbGUtb2ZmY2FudmFzIHNwYW46YWZ0ZXIge1xuICAgIGJhY2tncm91bmQ6ICNmZmY7XG4gICAgYm9yZGVyLXJhZGl1czogMXB4O1xuICAgIGNvbnRlbnQ6ICcnO1xuICAgIGRpc3BsYXk6IGJsb2NrO1xuICAgIGhlaWdodDogM3B4O1xuICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICB0cmFuc2l0aW9uOiBhbGwgMjUwbXMgZWFzZS1pbi1vdXQ7XG4gICAgd2lkdGg6IDI0cHg7IH1cbiAgLnRvZ2dsZS1vZmZjYW52YXMgc3BhbjpiZWZvcmUge1xuICAgIHRvcDogLTlweDsgfVxuICAudG9nZ2xlLW9mZmNhbnZhcyBzcGFuOmFmdGVyIHtcbiAgICBib3R0b206IC05cHg7IH1cbiAgLm5hdi1vcGVuIC50b2dnbGUtb2ZmY2FudmFzIHNwYW4ge1xuICAgIGJhY2tncm91bmQ6IHRyYW5zcGFyZW50OyB9XG4gICAgLm5hdi1vcGVuIC50b2dnbGUtb2ZmY2FudmFzIHNwYW46YmVmb3JlLFxuICAgIC5uYXYtb3BlbiAudG9nZ2xlLW9mZmNhbnZhcyBzcGFuOmFmdGVyIHtcbiAgICAgIHRvcDogMDsgfVxuICAgIC5uYXYtb3BlbiAudG9nZ2xlLW9mZmNhbnZhcyBzcGFuOmJlZm9yZSB7XG4gICAgICB0cmFuc2Zvcm06IHJvdGF0ZSg0NWRlZyk7IH1cbiAgICAubmF2LW9wZW4gLnRvZ2dsZS1vZmZjYW52YXMgc3BhbjphZnRlciB7XG4gICAgICB0cmFuc2Zvcm06IHJvdGF0ZSgtNDVkZWcpOyB9XG5cbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSBOYXZpZ2F0aW9uIC0tKi9cbi5oZWFkZXIgLm5hdiB7XG4gIGJveC1zaGFkb3c6IGluc2V0IC04cHggMCA4cHggLTZweCByZ2JhKDAsIDAsIDAsIDAuMik7XG4gIHBhZGRpbmc6IDMlOyB9XG4gIEBtZWRpYSBzY3JlZW4gYW5kIChtaW4td2lkdGg6IDc2OHB4KSB7XG4gICAgLmhlYWRlciAubmF2IHtcbiAgICAgIGJhY2tncm91bmQ6ICM1NTU7XG4gICAgICBib3gtc2hhZG93OiBub25lO1xuICAgICAgcGFkZGluZzogMS41JSAzJTsgfSB9XG4gIC5oZWFkZXIgLm5hdiAuYWN0aXZlIC5uYXYtbGlzdC1pdGVtLXRleHQge1xuICAgIGZvbnQtd2VpZ2h0OiBib2xkOyB9XG4gICAgQG1lZGlhIHNjcmVlbiBhbmQgKG1pbi13aWR0aDogNzY4cHgpIHtcbiAgICAgIC5oZWFkZXIgLm5hdiAuYWN0aXZlIC5uYXYtbGlzdC1pdGVtLXRleHQge1xuICAgICAgICBib3JkZXItYm90dG9tOiAxcHggc29saWQgcmdiYSgyNTUsIDI1NSwgMjU1LCAwLjUpOyB9IH1cbiAgLmhlYWRlciAubmF2LWxpc3Qge1xuICAgIGxpc3Qtc3R5bGU6IG5vbmU7XG4gICAgbWFyZ2luLWJvdHRvbTogMDtcbiAgICBwYWRkaW5nLWxlZnQ6IDA7IH1cbiAgICAuaGVhZGVyIC5uYXYtbGlzdCBhIHtcbiAgICAgIGRpc3BsYXk6IGJsb2NrO1xuICAgICAgcGFkZGluZzogNnB4OyB9XG4gICAgICAuaGVhZGVyIC5uYXYtbGlzdCBhOmhvdmVyLFxuICAgICAgLmhlYWRlciAubmF2LWxpc3QgYTphY3RpdmUsXG4gICAgICAuaGVhZGVyIC5uYXYtbGlzdCBhOmZvY3VzIHtcbiAgICAgICAgdGV4dC1kZWNvcmF0aW9uOiBub25lOyB9XG4gICAgQG1lZGlhIHNjcmVlbiBhbmQgKG1pbi13aWR0aDogNzY4cHgpIHtcbiAgICAgIC5oZWFkZXIgLm5hdi1saXN0IHtcbiAgICAgICAgZGlzcGxheTogZmxleDtcbiAgICAgICAgZmxleC13cmFwOiB3cmFwO1xuICAgICAgICBqdXN0aWZ5LWNvbnRlbnQ6IGNlbnRlcjtcbiAgICAgICAgcGFkZGluZzogMDtcbiAgICAgICAgd2lkdGg6IDEwMCU7IH1cbiAgICAgICAgLmhlYWRlciAubmF2LWxpc3QgYSxcbiAgICAgICAgLmhlYWRlciAubmF2LWxpc3QgYTpob3ZlcixcbiAgICAgICAgLmhlYWRlciAubmF2LWxpc3QgYTphY3RpdmUsXG4gICAgICAgIC5oZWFkZXIgLm5hdi1saXN0IGE6Zm9jdXMge1xuICAgICAgICAgIGNvbG9yOiAjZmZmOyB9XG4gICAgICAgIC5oZWFkZXIgLm5hdi1saXN0IGxpIHtcbiAgICAgICAgICBwYWRkaW5nOiAwIDIwcHg7IH0gfVxuXG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cdCAgICBGT09URVJcbi0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbi5mb290ZXIge1xuICBwYWRkaW5nOiAzJTtcbiAgdGV4dC1hbGlnbjogY2VudGVyOyB9XG4gIEBtZWRpYSBzY3JlZW4gYW5kIChtaW4td2lkdGg6IDc2OHB4KSB7XG4gICAgLmZvb3RlciB7XG4gICAgICBwYWRkaW5nOiAxLjUlIDMlOyB9IH1cblxuLyotLS0tLS0tLS0tLS0tLS0tLS0tLVxuXHQgICAgTE9BRElOR1xuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuLmxvYWRpbmctd3JhcHBlciB7XG4gIGhlaWdodDogMTAwJTtcbiAgcG9zaXRpb246IGZpeGVkO1xuICB0b3A6IDA7XG4gIGxlZnQ6IDA7XG4gIHdpZHRoOiAxMDAlO1xuICB6LWluZGV4OiA5OTk7IH1cblxuLmxvYWRpbmctb3ZlcmxheSB7XG4gIGJhY2tncm91bmQ6IHJnYmEoMjU1LCAyNTUsIDI1NSwgMC45KTtcbiAgcG9zaXRpb246IGFic29sdXRlO1xuICB0b3A6IDA7XG4gIGJvdHRvbTogMDtcbiAgd2lkdGg6IDEwMCU7IH1cblxuLmxvYWRpbmctc3Bpbm5lciB7XG4gIG1hcmdpbjogLTE2cHggMCAwIC0xNnB4O1xuICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gIHRvcDogNTAlO1xuICBsZWZ0OiA1MCU7IH1cblxuLyotLSBQYWdlcyAtLSovXG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cdCAgICA0MDRcbi0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbiIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cblx0ICAgRk9OVFNcbi0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cblxuLyogRnVsbCBAZm9udC1mYWNlIGRlY2xhcmF0aW9uICovXG5cbkBtZWRpYSBvbmx5IHNjcmVlbiBhbmQgKG1heC13aWR0aDogMzIwcHgpLCBzY3JlZW4gYW5kIChtYXgtZGV2aWNlLXdpZHRoOiA3MjBweCkgYW5kIChvcmllbnRhdGlvbjpwb3J0cmFpdCksIHNjcmVlbiBhbmQgKG1heC1kZXZpY2Utd2lkdGg6IDEyODBweCkgYW5kIChvcmllbnRhdGlvbjpsYW5kc2NhcGUpIHtcblx0LyogZm9yIG1vYmlsZSBkZXZpY2VzLCBvbmx5IGxvYWQgU1ZHIGZvbnRcblx0c2VlOiBodHRwOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzIwODkwNDg5L2ZvbnQtZmFjZS1kZWNsYXJhdGlvbnMtZG9udC13b3JrLWluLWFuZHJvaWQtNC0zLWludGVybmV0LWJyb3dzZXJcblxuXHRAZm9udC1mYWNlIHtcblx0XHRmb250LWZhbWlseTogJ0ZvbnROYW1lJztcblx0XHRzcmM6IHVybCgnLi4vZm9udHMvRm9udE5hbWUuc3ZnI0ZvbnROYW1lJykgZm9ybWF0KCdzdmcnKTtcblx0XHRmb250LXdlaWdodDogbm9ybWFsO1xuXHRcdGZvbnQtc3R5bGU6IG5vcm1hbDtcblx0fSAqL1xufSIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cblx0IFBSRVNFTlRBVElPTlxuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuXG5ib2R5IHtcblx0YmFja2dyb3VuZDogJGNvbG9yLWJnLWxpZ2h0O1xuXHRjb2xvcjogJGNvbG9yLXRleHQ7XG5cdGZvbnQ6IG5vcm1hbCAxNnB4LzEuMiAnSGVsdmV0aWNhIE5ldWUnLCBIZWx2ZXRpY2EsIEFyaWFsLCBWZXJkYW5hLCBzYW5zLXNlcmlmO1xufVxuLmxheW91dC1jYW52YXMge1xuXHRiYWNrZ3JvdW5kOiAkY29sb3ItYmc7XG59XG5cbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSBDb250ZW50IC0tKi9cblxuLmNvbnRlbnQtd3JhcHBlciB7XG5cdHBhZGRpbmc6ICRwYWRkaW5nLXNjcmVlbi1zbWFsbDtcblxuXHRAaW5jbHVkZSBtcSgkbXEtbGFyZ2UpIHtcblx0XHRwYWRkaW5nOiAkcGFkZGluZy1zY3JlZW4tbGFyZ2U7XG5cdH1cbn1cbi5jb250ZW50LWhlYWRpbmcge1xuXHRmb250LXNpemU6IDM2cHg7XG5cdG1hcmdpbjogLTEuNSUgMCAyMHB4O1xuXG5cdEBpbmNsdWRlIG1xKCRtcS1sYXJnZSkge1xuXHRcdG1hcmdpbi10b3A6IDA7XG5cdFx0dGV4dC1hbGlnbjogY2VudGVyO1xuXHR9XG59IiwiLyotLS0tLS0tLS0tLS0tLS0tLS0tLVxuIExBWU9VVCBGVU5DVElPTkFMSVRZXG4tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5cbmJvZHkge1xuXHRtaW4td2lkdGg6IDMyMHB4O1xufVxuLmxheW91dC1vdmVyZmxvdyB7XG5cdG92ZXJmbG93OiBoaWRkZW47XHQvKiBuZWNlc3NhcnkgdG8gaGFuZGxlIG9mZmNhbnZhcyBzY3JvbGxiYXIgYmVoYXZpb3IgKi9cbn1cblxuLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIE9mZi1jYW52YXMgRnVuY3Rpb25hbGl0eSAtLSovXG5cbi8qLS0gQ29udGVudCBjYW52YXMgd3JhcHBlciAtLSovXG5cbi5sYXlvdXQtY2FudmFzIHtcblx0YmFja2ZhY2UtdmlzaWJpbGl0eTogaGlkZGVuO1xuXHRwb3NpdGlvbjogcmVsYXRpdmU7XG5cdGxlZnQ6IDA7XG5cdHdpZHRoOiAxMDAlO1xuXG5cdEBpbmNsdWRlIG1xKCRtcS1sYXJnZSkge1xuXHRcdHRyYW5zaXRpb246IG5vbmU7XG5cdFx0dHJhbnNmb3JtOiBub25lO1xuXHR9XG5cdC5jc3N0cmFuc2Zvcm1zM2QgJiB7XG5cdFx0dHJhbnNpdGlvbjogdHJhbnNmb3JtIDI1MG1zIGVhc2U7XG5cdFx0dHJhbnNmb3JtOiB0cmFuc2xhdGUzZCgwLDAsMCk7XG5cdH1cblx0LmNzc3RyYW5zZm9ybXMzZCAubmF2LW9wZW4gJiB7XG5cdFx0dHJhbnNmb3JtOiB0cmFuc2xhdGUzZCgyNzBweCwwLDApO1xuXHR9XG5cdC5uby1jc3N0cmFuc2Zvcm1zM2QgLm5hdi1vcGVuICYge1xuXHRcdGxlZnQ6IDI3MHB4O1xuXHR9XG59XG5cbi8qLS0gSGVhZGVyIC0tKi9cblxuLmhlYWRlci1tb2JpbGUtcGFnZSB7XG5cdEBpbmNsdWRlIG1xKCRtcS1sYXJnZSkge1xuXHRcdGRpc3BsYXk6IG5vbmU7XG5cdH1cbn1cblxuLyotLSBOYXZpZ2F0aW9uIC0tKi9cblxuLmhlYWRlciAubmF2IHtcblx0YmFja2ZhY2UtdmlzaWJpbGl0eTogaGlkZGVuO1xuXHRkaXNwbGF5OiBub25lO1x0LyogZGVhbCB3aXRoIEZPVUMgKi9cblx0aGVpZ2h0OiAxMDAlO1xuXHRvdmVyZmxvdy15OiBhdXRvO1xuXHRwb3NpdGlvbjogYWJzb2x1dGU7XG5cdHRvcDogMDtcblx0d2lkdGg6IDI3MHB4O1xuXG5cdEBpbmNsdWRlIG1xKCRtcS1sYXJnZSkge1xuXHRcdGRpc3BsYXk6IGJsb2NrO1xuXHRcdHBvc2l0aW9uOiByZWxhdGl2ZTtcblx0XHR3aWR0aDogMTAwJTtcblx0fVxuXHQubmF2LWNsb3NlZCAmLFxuXHQubmF2LW9wZW4gJiB7XG5cdFx0ZGlzcGxheTogYmxvY2s7XHQvKiBkZWFsIHdpdGggRk9VQyAqL1xuXHR9XG5cdC5jc3N0cmFuc2Zvcm1zM2QgJiB7XG5cdFx0dHJhbnNmb3JtOiB0cmFuc2xhdGUzZCgtMTAwJSwwLDApO1xuXG5cdFx0QGluY2x1ZGUgbXEoJG1xLWxhcmdlKSB7XG5cdFx0XHR0cmFuc2Zvcm06IG5vbmU7XG5cdFx0fVxuXHR9XG5cdC5uby1jc3N0cmFuc2Zvcm1zM2QgLm5hdi1jbG9zZWQgJiB7XG5cdFx0bGVmdDogLTEwMCU7XG5cdH1cblx0Lm5vLWNzc3RyYW5zZm9ybXMzZCAubmF2LW9wZW4gJiB7XG5cdFx0bGVmdDogLTI3MHB4O1xuXG5cdFx0QGluY2x1ZGUgbXEoJG1xLWxhcmdlKSB7XG5cdFx0XHRsZWZ0OiAwO1xuXHRcdH1cblx0fVxufSIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cblx0ICAgUFJJTlRcbi0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cblxuQG1lZGlhIHByaW50IHtcblxuXHQqIHtcblx0XHRiYWNrZ3JvdW5kOiAjZmZmO1xuXHR9XG5cdGJvZHkge1xuXHRcdGNvbG9yOiAjMDAwO1xuXHRcdGZvbnQ6IG5vcm1hbCAxNnB4LzEuNCBHZW9yZ2lhLCAnVGltZXMgTmV3IFJvbWFuJywgc2VyaWY7XG5cdH1cblxuXHQvKi0tIEhpZGRlbiBFbGVtZW50cyAtLSovXG5cblx0LmhlYWRlcixcblx0LmZvb3RlciB7XG5cdFx0ZGlzcGxheTogbm9uZTtcblx0fVxuXG5cdC8qLS0gU2hvdyBsaW5rIFVSTHMgLS0qL1xuXG5cdGE6bGluayxcblx0YTp2aXNpdGVkIHtcblx0XHRjb2xvcjogYmx1ZTtcblx0XHR0ZXh0LWRlY29yYXRpb246IHVuZGVybGluZTtcblx0fVxuXHRhOmxpbms6YWZ0ZXIsXG5cdGE6dmlzaXRlZDphZnRlciB7XG5cdFx0Y29udGVudDpcIiBbXCIgYXR0cihocmVmKSBcIl0gXCI7XG5cdFx0Zm9udC1zaXplOiA3NSU7XG5cdH1cblxufSIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cblx0ICAgIEhFQURFUlxuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuXG4uaGVhZGVyLW1vYmlsZS1wYWdlIHtcblx0YmFja2dyb3VuZDogJGNvbG9yLW5hdi1iZztcblx0Y29sb3I6ICRjb2xvci13aGl0ZTtcblx0aGVpZ2h0OiA1MHB4O1xuXHRtYXJnaW4tYm90dG9tOiAxMHB4O1xuXHRwb3NpdGlvbjogcmVsYXRpdmU7XG5cblx0Ji1zaXRlVGl0bGUge1xuXHRcdGZvbnQtc2l6ZTogMzBweDtcblx0XHRsaW5lLWhlaWdodDogNTBweDtcblx0XHRtYXJnaW46IDA7XG5cdFx0cGFkZGluZzogMCAwIDAgNTBweDtcblx0XHRwb3NpdGlvbjogYWJzb2x1dGU7XG5cdFx0dG9wOiAwO1xuXHRcdHRleHQtYWxpZ246IGNlbnRlcjtcblx0XHR3aWR0aDogMTAwJTtcblx0fVxufSIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cblx0IE5BVklHQVRJT05cbi0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cblxuLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIEhhbWJ1cmdlciBtZW51IHRvZ2dsZSAtLSovXG5cbi50b2dnbGUtb2ZmY2FudmFzIHtcblx0YmFja2dyb3VuZDogJGNvbG9yLW5hdi1iZztcblx0Ym9yZGVyLXJpZ2h0OiAxcHggc29saWQgJGNvbG9yLWRpc2FibGVkLXJnYmE7XG5cdGRpc3BsYXk6IGlubGluZS1ibG9jaztcblx0aGVpZ2h0OiA1MHB4O1xuXHRwYWRkaW5nOiAyMy41cHggMTNweDtcblx0cG9zaXRpb246IHJlbGF0aXZlO1xuXHR0ZXh0LWFsaWduOiBjZW50ZXI7XG5cdHdpZHRoOiA1MHB4O1xuXHR6LWluZGV4OiAxMDA7XG5cblx0c3Bhbixcblx0c3BhbjpiZWZvcmUsXG5cdHNwYW46YWZ0ZXIge1xuXHRcdGJhY2tncm91bmQ6ICRjb2xvci13aGl0ZTtcblx0XHRib3JkZXItcmFkaXVzOiAxcHg7XG5cdFx0Y29udGVudDogJyc7XG5cdFx0ZGlzcGxheTogYmxvY2s7XG5cdFx0aGVpZ2h0OiAzcHg7XG5cdFx0cG9zaXRpb246IGFic29sdXRlO1xuXHRcdHRyYW5zaXRpb246IGFsbCAyNTBtcyBlYXNlLWluLW91dDtcblx0XHR3aWR0aDogMjRweDtcblx0fVxuXHRzcGFuIHtcblx0XHQmOmJlZm9yZSB7XG5cdFx0XHR0b3A6IC05cHg7XG5cdFx0fVxuXHRcdCY6YWZ0ZXIge1xuXHRcdFx0Ym90dG9tOiAtOXB4O1xuXHRcdH1cblx0fVxuXHQubmF2LW9wZW4gJiB7XG5cdFx0c3BhbiB7XG5cdFx0XHRiYWNrZ3JvdW5kOiB0cmFuc3BhcmVudDtcblxuXHRcdFx0JjpiZWZvcmUsXG5cdFx0XHQmOmFmdGVyIHtcblx0XHRcdFx0dG9wOiAwO1xuXHRcdFx0fVxuXHRcdFx0JjpiZWZvcmUge1xuXHRcdFx0XHR0cmFuc2Zvcm06IHJvdGF0ZSg0NWRlZyk7XG5cdFx0XHR9XG5cdFx0XHQmOmFmdGVyIHtcblx0XHRcdFx0dHJhbnNmb3JtOiByb3RhdGUoLTQ1ZGVnKTtcblx0XHRcdH1cblx0XHR9XG5cdH1cbn1cblxuLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIE5hdmlnYXRpb24gLS0qL1xuXG4uaGVhZGVyIC5uYXYge1xuXHRib3gtc2hhZG93OiBpbnNldCAtOHB4IDAgOHB4IC02cHggcmdiYSgwLDAsMCwwLjIpO1xuXHRwYWRkaW5nOiAkcGFkZGluZy1zY3JlZW4tc21hbGw7XG5cblx0QGluY2x1ZGUgbXEoJG1xLWxhcmdlKSB7XG5cdFx0YmFja2dyb3VuZDogJGNvbG9yLW5hdi1iZztcblx0XHRib3gtc2hhZG93OiBub25lO1xuXHRcdHBhZGRpbmc6ICRwYWRkaW5nLXNjcmVlbi1sYXJnZTtcblx0fVxuXG5cdC5hY3RpdmUgLm5hdi1saXN0LWl0ZW0tdGV4dCB7XG5cdFx0Zm9udC13ZWlnaHQ6IGJvbGQ7XG5cblx0XHRAaW5jbHVkZSBtcSgkbXEtbGFyZ2UpIHtcblx0XHRcdGJvcmRlci1ib3R0b206IDFweCBzb2xpZCAkY29sb3ItZGlzYWJsZWQtcmdiYTtcblx0XHR9XG5cdH1cblx0Ji1saXN0IHtcblx0XHRsaXN0LXN0eWxlOiBub25lO1xuXHRcdG1hcmdpbi1ib3R0b206IDA7XG5cdFx0cGFkZGluZy1sZWZ0OiAwO1xuXG5cdFx0YSB7XG5cdFx0XHRkaXNwbGF5OiBibG9jaztcblx0XHRcdHBhZGRpbmc6IDZweDtcblxuXHRcdFx0Jjpob3Zlcixcblx0XHRcdCY6YWN0aXZlLFxuXHRcdFx0Jjpmb2N1cyB7XG5cdFx0XHRcdHRleHQtZGVjb3JhdGlvbjogbm9uZTtcblx0XHRcdH1cblx0XHR9XG5cblx0XHRAaW5jbHVkZSBtcSgkbXEtbGFyZ2UpIHtcblx0XHRcdGRpc3BsYXk6IGZsZXg7XG5cdFx0XHRmbGV4LXdyYXA6IHdyYXA7XG5cdFx0XHRqdXN0aWZ5LWNvbnRlbnQ6IGNlbnRlcjtcblx0XHRcdHBhZGRpbmc6IDA7XG5cdFx0XHR3aWR0aDogMTAwJTtcblxuXHRcdFx0YSxcblx0XHRcdGE6aG92ZXIsXG5cdFx0XHRhOmFjdGl2ZSxcblx0XHRcdGE6Zm9jdXMge1xuXHRcdFx0XHRjb2xvcjogJGNvbG9yLXdoaXRlO1xuXHRcdFx0fVxuXHRcdFx0bGkge1xuXHRcdFx0XHRwYWRkaW5nOiAwIDIwcHg7XG5cdFx0XHR9XG5cdFx0fVxuXHR9XG59IiwiLyotLS0tLS0tLS0tLS0tLS0tLS0tLVxuXHQgICAgRk9PVEVSXG4tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5cbi5mb290ZXIge1xuXHRwYWRkaW5nOiAkcGFkZGluZy1zY3JlZW4tc21hbGw7XG5cdHRleHQtYWxpZ246IGNlbnRlcjtcblxuXHRAaW5jbHVkZSBtcSgkbXEtbGFyZ2UpIHtcblx0XHRwYWRkaW5nOiAkcGFkZGluZy1zY3JlZW4tbGFyZ2U7XG5cdH1cbn0iLCIvKi0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cdCAgICBMT0FESU5HXG4tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5cbi5sb2FkaW5nIHtcblx0Ji13cmFwcGVyIHtcblx0XHRoZWlnaHQ6IDEwMCU7XG5cdFx0cG9zaXRpb246IGZpeGVkO1xuXHRcdFx0dG9wOiAwOyBsZWZ0OiAwO1xuXHRcdHdpZHRoOiAxMDAlO1xuXHRcdHotaW5kZXg6IDk5OTtcblx0fVxuXHQmLW92ZXJsYXkge1xuXHRcdGJhY2tncm91bmQ6IHJnYmEoMjU1LDI1NSwyNTUsLjkpO1xuXHRcdHBvc2l0aW9uOiBhYnNvbHV0ZTtcblx0XHRcdHRvcDogMDsgYm90dG9tOiAwO1xuXHRcdHdpZHRoOiAxMDAlO1xuXHR9XG5cdCYtc3Bpbm5lciB7XG5cdFx0bWFyZ2luOiAtMTZweCAwIDAgLTE2cHg7XG5cdFx0cG9zaXRpb246IGFic29sdXRlO1xuXHRcdFx0dG9wOiA1MCU7IGxlZnQ6IDUwJTtcblx0fVxufSIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cblx0ICAgIDQwNFxuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuXG4uZXJyb3I0MDQtd3JhcHBlciB7XG5cbn0iXSwic291cmNlUm9vdCI6Ii9zb3VyY2UvIn0= */
\ No newline at end of file
+/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3N0ZGluIiwiY29yZS9wYXJ0aWFscy9fY29sb3JzLnZhcnMuc2NzcyIsImNvcmUvcGFydGlhbHMvX2xheW91dC52YXJzLnNjc3MiLCJjb3JlL3BhcnRpYWxzL19yZXNwb25zaXZlLnBhcnRpYWwuc2NzcyIsImNvcmUvX2Jhc2Uuc2NzcyIsInN0eWxlcy5jc3MiLCJjb3JlL19mb250cy5zY3NzIiwiY29yZS9fcHJlc2VudGF0aW9uLnNjc3MiLCJjb3JlL19sYXlvdXQuc2NzcyIsImNvcmUvX3ByaW50LnNjc3MiLCJtb2R1bGVzL19oZWFkZXIuc2NzcyIsIm1vZHVsZXMvX25hdi5zY3NzIiwibW9kdWxlcy9fZm9vdGVyLnNjc3MiLCJtb2R1bGVzL19sb2FkaW5nLnNjc3MiLCJwYWdlcy9fNDA0LnNjc3MiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7OztFQU1FO0FBRUYsY0FBYztBQ1JkOztzQkFFc0I7QUFFdEIsMkJBQTJCO0FBRTNCLG1EQUFtRDtBQVNuRCx3Q0FBd0M7QUFLeEMsMkJBQTJCO0FDcEIzQjs7c0JBRXNCO0FBRXRCLGlCQUFpQjtBQ0pqQjs7c0JBRXNCO0FBRXRCLG1CQUFtQjtBQUtuQixnQkFBZ0I7QUNUaEI7O3NCQUVzQjtBQUV0Qjs7Ozs7OztFQU9DLFVBQVU7RUFBRSxXQUFXO0VBQ3ZCLFVBQVU7RUFDVixpQkFBaUI7RUFBRSxhQUFhO0VBQ2hDLHFCQUFxQjtFQUFFLG9CQUFvQjtFQUFFLHFCQUFxQjtFQUNsRSxlQUFlO0VBQ2Ysc0JBQXNCO0VBQ3RCLHlCQUF5QixFQUN6Qjs7QUFDRDtFQUFPLG1CQUFtQjtFQUFFLCtCQUErQixFQUFJOztBQUMvRDtFQUFTLGlCQUFpQixFQUFJOztBQUM5QjtFQUFRLDBCQUEwQjtFQUFFLGtCQUFrQixFQUFJOztBQUMxRDtFQUFrQixpQkFBaUI7RUFBRSxvQkFBb0IsRUFBSTs7QUFDN0Q7RUFBVyxlQUFlLEVBQUk7O0FBQzlCO0VBQXlCLG9CQUFvQixFQUFJOztBQUNqRDtFQUFrRyxlQUFlLEVBQUk7O0FBQ3JIO0VBQ0MsdUJBQXVCLEVBQ3ZCOztBQUVEOztzQkFFc0I7QUFFdEIsbUJBQW1CO0FBRW5CO0VBQUssbUJBQW1CLEVBQUk7O0FBQzVCO0VBQVMsa0JBQWtCLEVBQUk7O0FBQy9CO0VBQWEsZ0RBQWdELEVBQUk7O0FBQ2pFO0VBQTRCLG9CQUFvQixFQUFJOztBQUNwRDtFQUFLLHlCQUF5QjtFQUFFLG1CQUFtQixFQUFJOztBQUN2RDtFQUFLLDRCQUE0QjtFQUFFLG1CQUFtQixFQUFJOztBQUMxRDtFQUEyRCxnQkFBZ0IsRUFBSTs7QUFDL0U7RUFBTSw4QkFBOEIsRUFBSTs7QUFDeEM7RUFBMEIsMEJBQTBCO0VBQUUsYUFBYSxFQUFJOztBQUN2RTtFQUFXLGlCQUFpQjtFQUFFLHVCQUF1QjtFQUFFLDhEQUE4RDtFQUFFLFlBQVksRUFBSTs7QUFDdkk7RUFBMEIsdUJBQXVCO0VBQUUscUJBQXFCO0VBQUUsbUJBQW1CO0VBQUUsaUJBQWlCLEVBQUk7O0FBQ3BIO0VBQWdCLHVCQUF1QixFQUFJOztBQUMzQztFQUFXLGVBQWUsRUFBSTs7QUFBQSw0REFBNEQ7QUFDMUY7RUFBa0IsZUFBZSxFQUFJOztBQUNyQztFQUFNLHNCQUFzQixFQUFJOztBQUNoQztFQUFNLG9CQUFvQixFQUFJOztBQUM5QjtFQUFRLGVBQWU7RUFBRSxhQUFhLEVBQUk7O0FBQzFDO0VBQVEsOEJIekNZLEVHeUM4Qjs7QUFDbEQ7RUFBSyxrQkFBa0IsRUFBSTs7QUFDM0I7RUFBYSxrQkFBa0IsRUFBSTs7QUFDbkM7RUFBSyw4Qkg1Q2UsRUc0Q3lCOztBQUU3QyxrQkFBa0I7QUFFbEI7O0VBRUMsYUFBYTtFQUNiLGVBQWUsRUFDZjs7QUFDRDtFQUFrQixZQUFZLEVBQUk7O0FBQ2xDO0VBQWlCLFFBQVEsRUFBSTs7QUFFN0IsZ0VBQWdFO0FDZ0doRTs7Ozs7O0VEeEZDLHlCQUF5QixFQUN6Qjs7QUFFRCwyQkFBMkI7QUFFM0I7RUFDQyxpQkFBaUI7RUFDakIsa0JBQWtCO0VBQ2xCLG9CQUFvQixFQUNwQjs7QUFFRCxtQ0FBbUM7QUFFbkM7RUFDQyxnQkFBZ0I7RUFDaEIsZ0JBQWdCLEVBQ2hCOztBQUVELGVBQWU7QUFFZjs7OztFQUlDLGdCQUFnQjtFQUFFLG9CQUFvQixFQUN0Qzs7QUFDRDtFQUNDLHFDSHJGb0I7RUdzRnBCLDJDSHRGb0IsRUd1RnBCOztBQUNEO0VBQ0MsbUJBQW1CO0VBQ25CLHNCQUFzQjtFQUN0QixtQkFBbUI7RUFDbkIsaUJBQWlCO0VBQ2pCLHVCQUF1QixFQUN2Qjs7QUVoSEQ7O3NCQUVzQjtBQUV0QixpQ0FBaUM7QUNKakM7O3NCQUVzQjtBQUV0QjtFQUNDLGlCTlFtQjtFTVBuQixZTklxQjtFTUhyQiw4RUFBOEUsRUFDOUU7O0FBQ0Q7RUFDQyxpQk5EaUIsRU1FakI7O0FBRUQsd0ZBQXdGO0FBRXhGO0VBQ0MsWUxWd0IsRUtleEI7RUpUQTtJSUdEO01BSUUsaUJMWjRCLEVLYzdCLEVBQUE7O0FBQ0Q7RUFDQyxnQkFBZ0I7RUFDaEIscUJBQXFCLEVBTXJCO0VKbEJBO0lJVUQ7TUFLRSxjQUFjO01BQ2QsbUJBQW1CLEVBRXBCLEVBQUE7O0FDOUJEOztzQkFFc0I7QUFFdEI7RUFDQyxpQkFBaUIsRUFDakI7O0FBQ0Q7RUFDQyxpQkFBaUI7RUFBRSxzREFBc0QsRUFDekU7O0FBRUQseUdBQXlHO0FBRXpHLGdDQUFnQztBQUVoQztFQUNDLG9DQUE0QjtFQUE1Qiw0QkFBNEI7RUFDNUIsbUJBQW1CO0VBQ25CLFFBQVE7RUFDUixZQUFZLEVBZ0JaO0VMdkJBO0lLR0Q7TUFPRSx5QkFBaUI7TUFBakIsaUJBQWlCO01BQ2pCLHdCQUFnQjtNQUFoQixvQkFBZ0I7TUFBaEIsZ0JBQWdCLEVBWWpCLEVBQUE7RUFWQTtJQUNDLGlEQUFpQztJQUFqQyxpQ0FBaUM7SUFDakMsd0NBQXNCO0lBQXRCLGdDQUFzQixFQUN0QjtFQUNEO0lBQ0MsNENBQXNCO0lBQXRCLG9DQUFzQixFQUN0QjtFQUNEO0lBQ0MsWUFBWSxFQUNaOztBQUdGLGdCQUFnQjtBTHpCZjtFSzJCRDtJQUVFLGNBQWMsRUFFZixFQUFBOztBQUVELG9CQUFvQjtBQUVwQjtFQUNDLG9DQUE0QjtFQUE1Qiw0QkFBNEI7RUFDNUIsY0FBYztFQUFFLG9CQUFvQjtFQUNwQyxhQUFhO0VBQ2IsaUJBQWlCO0VBQ2pCLG1CQUFtQjtFQUNuQixPQUFPO0VBQ1AsYUFBYSxFQTRCYjtFTHRFQTtJS21DRDtNQVVFLGVBQWU7TUFDZixtQkFBbUI7TUFDbkIsWUFBWSxFQXVCYixFQUFBO0VBckJBOztJQUVDLGVBQWU7SUFBRSxvQkFBb0IsRUFDckM7RUFDRDtJQUNDLDRDQUFzQjtJQUF0QixvQ0FBc0IsRUFLdEI7SUwzREQ7TUtxREE7UUFJRSx3QkFBZ0I7UUFBaEIsb0JBQWdCO1FBQWhCLGdCQUFnQixFQUVqQixFQUFBO0VBQ0Q7SUFDQyxZQUFZLEVBQ1o7RUFDRDtJQUNDLGFBQWEsRUFLYjtJTHJFRDtNSytEQTtRQUlFLFFBQVEsRUFFVCxFQUFBOztBQ2pGRjs7c0JBRXNCO0FBRXRCO0VBRUM7SUFDQyxpQkFBaUIsRUFDakI7RUFDRDtJQUNDLFlBQVk7SUFDWix3REFBd0QsRUFDeEQ7RUFFRCx5QkFBeUI7RUFFekI7O0lBRUMsY0FBYyxFQUNkO0VBRUQsd0JBQXdCO0VBRXhCOztJQUVDLFlBQVk7SUFDWiwyQkFBMkIsRUFDM0I7RUFDRDs7SUFFQyw4QkFBNEI7SUFDNUIsZUFBZSxFQUNmLEVBQUE7O0FUWEYsaUJBQWlCO0FVckJqQjs7c0JBRXNCO0FBRXRCO0VBQ0MsaUJUTXFCO0VTTHJCLFlUR2lCO0VTRmpCLGFBQWE7RUFDYixvQkFBb0I7RUFDcEIsbUJBQW1CLEVBWW5CO0VBakJEO0lBUUUsZ0JBQWdCO0lBQ2hCLGtCQUFrQjtJQUNsQixVQUFVO0lBQ1Ysb0JBQW9CO0lBQ3BCLG1CQUFtQjtJQUNuQixPQUFPO0lBQ1AsbUJBQW1CO0lBQ25CLFlBQVksRUFDWjs7QUNwQkY7O3NCQUVzQjtBQUV0QixzR0FBc0c7QUFFdEc7RUFDQyxpQlZJcUI7RVVIckIsaURWU3FCO0VVUnJCLHNCQUFzQjtFQUN0QixhQUFhO0VBQ2IscUJBQXFCO0VBQ3JCLG1CQUFtQjtFQUNuQixtQkFBbUI7RUFDbkIsWUFBWTtFQUNaLGFBQWEsRUFzQ2I7RUEvQ0Q7OztJQWNFLGlCVlhnQjtJVVloQixtQkFBbUI7SUFDbkIsWUFBWTtJQUNaLGVBQWU7SUFDZixZQUFZO0lBQ1osbUJBQW1CO0lBQ25CLDBDQUFrQztJQUFsQyxrQ0FBa0M7SUFDbEMsWUFBWSxFQUNaO0VBdEJGO0lBeUJHLFVBQVUsRUFDVjtFQTFCSDtJQTRCRyxhQUFhLEVBQ2I7RUFFRjtJQUVFLHdCQUF3QixFQVl4QjtJQWRGO01BTUcsT0FBTyxFQUNQO0lBUEg7TUFTRyxpQ0FBaUI7TUFBakIsNkJBQWlCO01BQWpCLHlCQUFpQixFQUNqQjtJQVZIO01BWUcsa0NBQWlCO01BQWpCLDhCQUFpQjtNQUFqQiwwQkFBaUIsRUFDakI7O0FBS0osMkZBQTJGO0FBRTNGO0VBQ0MscURBQXNDO0VBQ3RDLFlUckR3QixFU3NHeEI7RVJoR0E7SVE2Q0Q7TUFLRSxpQlZuRG9CO01Vb0RwQixpQkFBaUI7TUFDakIsaUJUekQ0QixFU3FHN0IsRUFBQTtFQW5ERDtJQVdFLGtCQUFrQixFQUtsQjtJUjdERDtNUTZDRDtRQWNHLGtEVnREbUIsRVV3RHBCLEVBQUE7RUFoQkY7SUFrQkUsaUJBQWlCO0lBQ2pCLGlCQUFpQjtJQUNqQixnQkFBZ0IsRUE4QmhCO0lBbERGO01BdUJHLGVBQWU7TUFDZixhQUFhLEVBT2I7TUEvQkg7UUE2Qkksc0JBQXNCLEVBQ3RCO0lSM0VIO01RNkNEO1FBa0NHLHFCQUFjO1FBQWQsc0JBQWM7UUFBZCxxQkFBYztRQUFkLGNBQWM7UUFDZCx3QkFBZ0I7UUFBaEIsb0JBQWdCO1FBQWhCLGdCQUFnQjtRQUNoQix5QkFBd0I7UUFBeEIsZ0NBQXdCO1FBQXhCLHNCQUF3QjtRQUF4Qix3QkFBd0I7UUFDeEIsV0FBVztRQUNYLFlBQVksRUFZYjtRQWxERjs7OztVQTRDSSxZVjVGYyxFVTZGZDtRQTdDSjtVQStDSSxnQkFBZ0IsRUFDaEIsRUFBQTs7QUN6R0o7O3NCQUVzQjtBQUV0QjtFQUNDLFlWQ3dCO0VVQXhCLG1CQUFtQixFQUtuQjtFVENBO0lTUkQ7TUFLRSxpQlZGNEIsRVVJN0IsRUFBQTs7QUNYRDs7c0JBRXNCO0FBRXRCO0VBRUUsYUFBYTtFQUNiLGdCQUFnQjtFQUNmLE9BQU87RUFBRSxRQUFRO0VBQ2xCLFlBQVk7RUFDWixhQUFhLEVBQ2I7O0FBUEY7RUFTRSxxQ0FBZ0I7RUFDaEIsbUJBQW1CO0VBQ2xCLE9BQU87RUFBRSxVQUFVO0VBQ3BCLFlBQVksRUFDWjs7QUFiRjtFQWVFLHdCQUF3QjtFQUN4QixtQkFBbUI7RUFDbEIsU0FBUztFQUFFLFVBQVUsRUFDdEI7O0FiTUYsZUFBZTtBYzVCZjs7c0JBRXNCIiwiZmlsZSI6InN0eWxlcy5jc3MiLCJzb3VyY2VzQ29udGVudCI6WyIvKlxyXG4gKiByZVN0YXJ0IEJvaWxlcnBsYXRlIC8gcmVTdGFydCBBbmd1bGFyXHJcbiAqIEF1dGhvcjogS2ltIE1haWRhIFxyXG4gKiBBdXRob3IgVVJJOiA8aHR0cDovL2tpbS1tYWlkYS5jb20+XHJcbiAqIFNvdXJjZTogPGh0dHBzOi8vZ2l0aHViLmNvbS9rbWFpZGEvcmVTdGFydC1hbmd1bGFyPlxyXG4gKiBMaWNlbnNlOiBHTlUgUHVibGljIExpY2Vuc2VcclxuKi9cclxuXHJcbi8qLS0gQ29yZSAtLSovXHJcblxyXG4vLyBwYXJ0aWFsc1xyXG5AaW1wb3J0ICdjb3JlL3BhcnRpYWxzL2NvbG9ycy52YXJzJztcclxuQGltcG9ydCAnY29yZS9wYXJ0aWFscy9sYXlvdXQudmFycyc7XHJcbkBpbXBvcnQgJ2NvcmUvcGFydGlhbHMvcmVzcG9uc2l2ZS5wYXJ0aWFsJztcclxuXHJcbkBpbXBvcnQgJ2NvcmUvYmFzZSc7XHJcbkBpbXBvcnQgJ2NvcmUvZm9udHMnO1xyXG5AaW1wb3J0ICdjb3JlL3ByZXNlbnRhdGlvbic7XHJcbkBpbXBvcnQgJ2NvcmUvbGF5b3V0JztcclxuQGltcG9ydCAnY29yZS9wcmludCc7XHJcblxyXG4vKi0tIE1vZHVsZXMgLS0qL1xyXG5cclxuQGltcG9ydCAnbW9kdWxlcy9oZWFkZXInO1xyXG5AaW1wb3J0ICdtb2R1bGVzL25hdic7XHJcbkBpbXBvcnQgJ21vZHVsZXMvZm9vdGVyJztcclxuQGltcG9ydCAnbW9kdWxlcy9sb2FkaW5nJztcclxuXHJcbi8qLS0gUGFnZXMgLS0qL1xyXG5cclxuQGltcG9ydCAncGFnZXMvNDA0JzsiLCIvKi0tLS0tLS0tLS0tLS0tLS0tLS0tXHJcblx0Q09MT1IgVkFSSUFCTEVTXHJcbi0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cclxuXHJcbi8qLS0gQ29sb3IgZGVmaW5pdGlvbnMgLS0qL1xyXG5cclxuLyogSGV4IGNvZGUgbmFtZXMgZnJvbSBodHRwOi8vbmFtZS1vZi1jb2xvci5jb20vICovXHJcblxyXG4kY29sb3ItYmxhY2s6ICMwMDA7XHJcbiRjb2xvci13aGl0ZTogI2ZmZjtcclxuJGNvbG9yLW1pbmVTaGFmdDogIzMzMztcclxuJGNvbG9yLWRhdnlzR3JheTogIzU1NTtcclxuJGNvbG9yLWNlbGVzdGU6ICNjY2M7XHJcbiRjb2xvci1nYWxsZXJ5OiAjZWVlO1xyXG5cclxuLyogQ29sb3IgcGFydGlhbGx5IGRlZmluZWQgYnkgb3BhY2l0eSAqL1xyXG5cclxuJGNvbG9yLWdyYXktcmdiYTogcmdiYSgyNTUsMjU1LDI1NSwuNSk7XHJcbiRjb2xvci1yZWQtcmdiYTogcmdiYSgxNjksNjgsNjYsLjYpO1xyXG5cclxuLyotLSBDb2xvciBieSBmdW5jdGlvbiAtLSovXHJcblxyXG4kY29sb3ItYmctbGlnaHQ6ICRjb2xvci1nYWxsZXJ5O1xyXG4kY29sb3ItYmc6ICRjb2xvci13aGl0ZTtcclxuJGNvbG9yLWJvcmRlcjogJGNvbG9yLWNlbGVzdGU7XHJcbiRjb2xvci1tb2R1bGUtYmc6ICRjb2xvci1jZWxlc3RlO1xyXG4kY29sb3ItbmF2LWJnOiAkY29sb3ItZGF2eXNHcmF5O1xyXG4kY29sb3ItdGV4dDogJGNvbG9yLW1pbmVTaGFmdDtcclxuJGNvbG9yLWxpbmstY29udHJhc3Q6ICRjb2xvci13aGl0ZTtcclxuJGNvbG9yLWRpc2FibGVkLXJnYmE6ICRjb2xvci1ncmF5LXJnYmE7XHJcbiRjb2xvci1pbnZhbGlkLXJnYmE6ICRjb2xvci1yZWQtcmdiYTsiLCIvKi0tLS0tLS0tLS0tLS0tLS0tLS0tXHJcblx0TEFZT1VUIFZBUklBQkxFU1xyXG4tLS0tLS0tLS0tLS0tLS0tLS0tLSovXHJcblxyXG4vKi0tIFBhZGRpbmcgLS0qL1xyXG5cclxuJHBhZGRpbmctc2NyZWVuLXNtYWxsOiAzJTtcclxuJHBhZGRpbmctc2NyZWVuLWxhcmdlOiAxLjUlIDMlOyIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cclxuXHQgIFJFU1BPTlNJVkVcclxuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xyXG5cclxuLyotLSBWYXJpYWJsZXMgLS0qL1xyXG5cclxuJG1xLXNtYWxsOiAnc2NyZWVuIGFuZCAobWF4LXdpZHRoOiA3NjdweCknO1xyXG4kbXEtbGFyZ2U6ICdzY3JlZW4gYW5kIChtaW4td2lkdGg6IDc2OHB4KSc7XHJcblxyXG4vKi0tIE1peGlucyAtLSovXHJcblxyXG5AbWl4aW4gbXEoJG1xU3RyaW5nKSB7XHJcblx0QG1lZGlhICN7JG1xU3RyaW5nfSB7XHJcblx0XHRAY29udGVudDtcclxuXHR9XHJcbn0iLCIvKi0tLS0tLS0tLS0tLS0tLS0tLS0tXHJcblx0ICBDU1MgUkVTRVRcclxuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xyXG5cclxuaHRtbCwgYm9keSwgZGl2LCBzcGFuLCBoMSwgaDIsIGgzLCBoNCwgaDUsIGg2LCBwLCBibG9ja3F1b3RlLCBwcmUsXHJcbmEsIGFiYnIsIGFjcm9ueW0sIGFkZHJlc3MsIGJpZywgY2l0ZSwgY29kZSwgZGVsLCBkZm4sIGVtLCBpbWcsIGlucyxcclxua2JkLCBxLCBzLCBzYW1wLCBzbWFsbCwgc3Ryb25nLCB0dCwgdmFyLCBkbCwgZHQsXHJcbmRkLCBvbCwgdWwsIGxpLCBmaWVsZHNldCwgZm9ybSwgaW5wdXQsIGJ1dHRvbiwgdGV4dGFyZWEsIGxhYmVsLCBsZWdlbmQsIHRhYmxlLCBjYXB0aW9uLFxyXG50Ym9keSwgdGZvb3QsIHRoZWFkLCB0ciwgdGgsIHRkLFxyXG5hcnRpY2xlLCBhc2lkZSwgY2FudmFzLCBkZXRhaWxzLCBmaWdjYXB0aW9uLCBmaWd1cmUsXHJcbmZvb3RlciwgaGVhZGVyLCBoZ3JvdXAsIG1lbnUsIG5hdiwgc2VjdGlvbiB7XHJcblx0bWFyZ2luOiAwOyBwYWRkaW5nOiAwO1xyXG5cdGJvcmRlcjogMDtcclxuXHRib3JkZXItcmFkaXVzOiAwO1x0LyogZm9yIGlPUyAqL1xyXG5cdGZvbnQtd2VpZ2h0OiBpbmhlcml0OyBmb250LXN0eWxlOiBpbmhlcml0OyBmb250LWZhbWlseTogaW5oZXJpdDtcclxuXHRsaW5lLWhlaWdodDogMTtcclxuXHR0ZXh0LWRlY29yYXRpb246IG5vbmU7XHJcblx0dmVydGljYWwtYWxpZ246IGJhc2VsaW5lO1xyXG59XHJcbmh0bWwgeyBvdmVyZmxvdy15OiBzY3JvbGw7IC13ZWJraXQtdGV4dC1zaXplLWFkanVzdDogMTAwJTsgfVxyXG5vbCwgdWwgeyBsaXN0LXN0eWxlOiBub25lOyB9XHJcbnRhYmxlIHsgYm9yZGVyLWNvbGxhcHNlOiBjb2xsYXBzZTsgYm9yZGVyLXNwYWNpbmc6IDA7IH1cclxuY2FwdGlvbiwgdGgsIHRkIHsgdGV4dC1hbGlnbjogbGVmdDsgZm9udC13ZWlnaHQ6IG5vcm1hbDsgfVxyXG5zdXAsIHN1YiB7IGxpbmUtaGVpZ2h0OiAxOyB9XHJcbmgxLCBoMiwgaDMsIGg0LCBoNSwgaDYgeyBmb250LXdlaWdodDogbm9ybWFsOyB9XHJcbmFydGljbGUsIGFzaWRlLCBjYW52YXMsIGRldGFpbHMsIGZpZ2NhcHRpb24sIGZpZ3VyZSwgZm9vdGVyLCBoZWFkZXIsIGhncm91cCwgbWVudSwgbmF2LCBzZWN0aW9uIHsgZGlzcGxheTogYmxvY2s7IH1cclxuKiwqOmJlZm9yZSwqOmFmdGVyIHtcclxuXHRib3gtc2l6aW5nOiBib3JkZXItYm94O1xyXG59XHJcblxyXG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tXHJcblx0ICAgQkFTSUNTXHJcbi0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cclxuXHJcbi8qLS0gTm9ybWFsaXplIC0tKi9cclxuXHJcbmVtIHsgZm9udC1zdHlsZTogaXRhbGljOyB9XHJcbnN0cm9uZyB7IGZvbnQtd2VpZ2h0OiBib2xkOyB9XHJcbnE6bGFuZyhlbikgeyBxdW90ZXM6ICcmIzgyMjA7JyAnJiM4MjIxOycgJyYjODIxNjsnICcmIzgyMTc7JzsgfVxyXG5oMSwgaDIsIGgzLCBoNCwgaDUsIGg2LCBwIHsgbWFyZ2luLWJvdHRvbTogMTVweDsgfVxyXG51bCB7IGxpc3Qtc3R5bGU6IG91dHNpZGUgZGlzYzsgcGFkZGluZy1sZWZ0OiAyNHB4OyB9XHJcbm9sIHsgbGlzdC1zdHlsZTogb3V0c2lkZSBkZWNpbWFsOyBwYWRkaW5nLWxlZnQ6IDI0cHg7IH1cclxuYSwgbGFiZWwsIGlucHV0W3R5cGU9YnV0dG9uXSwgaW5wdXRbdHlwZT1zdWJtaXRdLCBidXR0b24geyBjdXJzb3I6IHBvaW50ZXI7IH1cclxuZGVsIHsgdGV4dC1kZWNvcmF0aW9uOiBsaW5lLXRocm91Z2g7IH1cclxuYWJiclt0aXRsZV0sIGRmblt0aXRsZV0geyBib3JkZXItYm90dG9tOiAxcHggZG90dGVkOyBjdXJzb3I6IGhlbHA7IH1cclxucHJlLGNvZGUgeyBiYWNrZ3JvdW5kOiAjZWVlOyBib3JkZXI6IDFweCBzb2xpZCAjY2NjOyBmb250LWZhbWlseTogQ29uc29sYXMsICdMdWNpZGEgQ29uc29sZScsICdDb3VyaWVyIE5ldycsIHNlcmlmOyBwYWRkaW5nOiAyJTsgfVxyXG5pbnB1dCwgc2VsZWN0LCB0ZXh0YXJlYSB7IGJvcmRlcjogMXB4IHNvbGlkICNjY2M7IGZvbnQtZmFtaWx5OiBpbmhlcml0OyBmb250LXNpemU6IGluaGVyaXQ7IHBhZGRpbmc6IDNweCA2cHg7IH1cclxuaW5wdXQsIHNlbGVjdCB7IHZlcnRpY2FsLWFsaWduOiBtaWRkbGU7IH1cclxudGV4dGFyZWEgeyBvdmVyZmxvdzogYXV0bzsgfSAvKiBwcmV2ZW50cyBzY3JvbGxiYXIgZnJvbSBzaG93aW5nIHVwIHdoZW4gdW5uZWVkZWQgaW4gSUUgKi9cclxuc21hbGwsIHN1cCwgc3ViIHsgZm9udC1zaXplOiA4NSU7IH1cclxuc3VwIHsgdmVydGljYWwtYWxpZ246IHN1cGVyOyB9XHJcbnN1YiB7IHZlcnRpY2FsLWFsaWduOiBzdWI7IH1cclxudGFibGUgeyBtYXJnaW46IDEwcHggMDsgcGFkZGluZzogM3B4OyB9XHJcbnRoZWFkIHsgYm9yZGVyLWJvdHRvbTogMnB4IHNvbGlkICRjb2xvci1ib3JkZXI7IH1cclxudGggeyBmb250LXdlaWdodDogYm9sZDsgfVxyXG50aCwgdHIsIHRkIHsgcGFkZGluZzogNHB4IDEycHg7IH1cclxudHIgeyBib3JkZXItYm90dG9tOiAxcHggc29saWQgJGNvbG9yLWJvcmRlcn1cclxuXHJcbi8qLS0gQ2xlYXJmaXggLS0qL1xyXG5cclxuLmNsZWFyZml4OmJlZm9yZSxcclxuLmNsZWFyZml4OmFmdGVyIHtcclxuXHRjb250ZW50OiBcIiBcIjtcclxuXHRkaXNwbGF5OiB0YWJsZTtcclxufVxyXG4uY2xlYXJmaXg6YWZ0ZXIgeyBjbGVhcjogYm90aDsgfVxyXG4uaWU3IC5jbGVhcmZpeCB7IHpvb206IDE7IH1cclxuXHJcbi8qLS0gbmctY2xvYWs6IHByZXZlbnQgRk9VQyBiZWZvcmUgQW5ndWxhciBKYXZhU2NyaXB0IGxvYWRzIC0tKi9cclxuXHJcbltuZ1xcOmNsb2FrXSxcclxuW25nLWNsb2FrXSxcclxuW2RhdGEtbmctY2xvYWtdLFxyXG5beC1uZy1jbG9ha10sXHJcbi5uZy1jbG9hayxcclxuLngtbmctY2xvYWsge1xyXG5cdGRpc3BsYXk6IG5vbmUgIWltcG9ydGFudDtcclxufVxyXG5cclxuLyotLSBJbWFnZSBSZXBsYWNlbWVudCAtLSovXHJcblxyXG4uaXIge1xyXG5cdG92ZXJmbG93OiBoaWRkZW47XHJcblx0dGV4dC1pbmRlbnQ6IDIwMCU7XHJcblx0d2hpdGUtc3BhY2U6IG5vd3JhcDtcclxufVxyXG5cclxuLyotLSBJbmxpbmUgbGluayB0b3VjaCB0YXJnZXRzIC0tKi9cclxuXHJcbi50b3VjaCBwIGEge1xyXG5cdG1hcmdpbjogMCAtLjVlbTtcclxuXHRwYWRkaW5nOiAwIC41ZW07XHJcbn1cclxuXHJcbi8qLS0gRm9ybXMgLS0qL1xyXG5cclxuaW5wdXRbdHlwZT1cInRleHRcIl0sXHJcbmlucHV0W3R5cGU9XCJudW1iZXJcIl0sXHJcbmlucHV0W3R5cGU9XCJwYXNzd29yZFwiXSxcclxudGV4dGFyZWEge1xyXG5cdGZvbnQtc2l6ZTogMTZweDtcdC8qIGZvciBpT1MgcGhvbmVzICovXHJcbn1cclxuaW5wdXQubmctZGlydHkubmctaW52YWxpZCB7XHJcblx0Ym9yZGVyLWNvbG9yOiAkY29sb3ItaW52YWxpZC1yZ2JhO1xyXG5cdGJveC1zaGFkb3c6IDAgMCA2cHggJGNvbG9yLWludmFsaWQtcmdiYTtcclxufVxyXG5idXR0b24ge1xyXG5cdGJvcmRlci1yYWRpdXM6IDRweDtcclxuXHRkaXNwbGF5OiBpbmxpbmUtYmxvY2s7XHJcblx0Zm9udC1zaXplOiBpbmhlcml0O1xyXG5cdHBhZGRpbmc6IDVweCA0cHg7XHJcblx0dmVydGljYWwtYWxpZ246IG1pZGRsZTtcclxufSIsIi8qXHJcbiAqIHJlU3RhcnQgQm9pbGVycGxhdGUgLyByZVN0YXJ0IEFuZ3VsYXJcclxuICogQXV0aG9yOiBLaW0gTWFpZGEgXHJcbiAqIEF1dGhvciBVUkk6IDxodHRwOi8va2ltLW1haWRhLmNvbT5cclxuICogU291cmNlOiA8aHR0cHM6Ly9naXRodWIuY29tL2ttYWlkYS9yZVN0YXJ0LWFuZ3VsYXI+XHJcbiAqIExpY2Vuc2U6IEdOVSBQdWJsaWMgTGljZW5zZVxyXG4qL1xuLyotLSBDb3JlIC0tKi9cbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cclxuXHRDT0xPUiBWQVJJQUJMRVNcclxuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuLyotLSBDb2xvciBkZWZpbml0aW9ucyAtLSovXG4vKiBIZXggY29kZSBuYW1lcyBmcm9tIGh0dHA6Ly9uYW1lLW9mLWNvbG9yLmNvbS8gKi9cbi8qIENvbG9yIHBhcnRpYWxseSBkZWZpbmVkIGJ5IG9wYWNpdHkgKi9cbi8qLS0gQ29sb3IgYnkgZnVuY3Rpb24gLS0qL1xuLyotLS0tLS0tLS0tLS0tLS0tLS0tLVxyXG5cdExBWU9VVCBWQVJJQUJMRVNcclxuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuLyotLSBQYWRkaW5nIC0tKi9cbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cclxuXHQgIFJFU1BPTlNJVkVcclxuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuLyotLSBWYXJpYWJsZXMgLS0qL1xuLyotLSBNaXhpbnMgLS0qL1xuLyotLS0tLS0tLS0tLS0tLS0tLS0tLVxyXG5cdCAgQ1NTIFJFU0VUXHJcbi0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbmh0bWwsIGJvZHksIGRpdiwgc3BhbiwgaDEsIGgyLCBoMywgaDQsIGg1LCBoNiwgcCwgYmxvY2txdW90ZSwgcHJlLFxuYSwgYWJiciwgYWNyb255bSwgYWRkcmVzcywgYmlnLCBjaXRlLCBjb2RlLCBkZWwsIGRmbiwgZW0sIGltZywgaW5zLFxua2JkLCBxLCBzLCBzYW1wLCBzbWFsbCwgc3Ryb25nLCB0dCwgdmFyLCBkbCwgZHQsXG5kZCwgb2wsIHVsLCBsaSwgZmllbGRzZXQsIGZvcm0sIGlucHV0LCBidXR0b24sIHRleHRhcmVhLCBsYWJlbCwgbGVnZW5kLCB0YWJsZSwgY2FwdGlvbixcbnRib2R5LCB0Zm9vdCwgdGhlYWQsIHRyLCB0aCwgdGQsXG5hcnRpY2xlLCBhc2lkZSwgY2FudmFzLCBkZXRhaWxzLCBmaWdjYXB0aW9uLCBmaWd1cmUsXG5mb290ZXIsIGhlYWRlciwgaGdyb3VwLCBtZW51LCBuYXYsIHNlY3Rpb24ge1xuICBtYXJnaW46IDA7XG4gIHBhZGRpbmc6IDA7XG4gIGJvcmRlcjogMDtcbiAgYm9yZGVyLXJhZGl1czogMDtcbiAgLyogZm9yIGlPUyAqL1xuICBmb250LXdlaWdodDogaW5oZXJpdDtcbiAgZm9udC1zdHlsZTogaW5oZXJpdDtcbiAgZm9udC1mYW1pbHk6IGluaGVyaXQ7XG4gIGxpbmUtaGVpZ2h0OiAxO1xuICB0ZXh0LWRlY29yYXRpb246IG5vbmU7XG4gIHZlcnRpY2FsLWFsaWduOiBiYXNlbGluZTsgfVxuXG5odG1sIHtcbiAgb3ZlcmZsb3cteTogc2Nyb2xsO1xuICAtd2Via2l0LXRleHQtc2l6ZS1hZGp1c3Q6IDEwMCU7IH1cblxub2wsIHVsIHtcbiAgbGlzdC1zdHlsZTogbm9uZTsgfVxuXG50YWJsZSB7XG4gIGJvcmRlci1jb2xsYXBzZTogY29sbGFwc2U7XG4gIGJvcmRlci1zcGFjaW5nOiAwOyB9XG5cbmNhcHRpb24sIHRoLCB0ZCB7XG4gIHRleHQtYWxpZ246IGxlZnQ7XG4gIGZvbnQtd2VpZ2h0OiBub3JtYWw7IH1cblxuc3VwLCBzdWIge1xuICBsaW5lLWhlaWdodDogMTsgfVxuXG5oMSwgaDIsIGgzLCBoNCwgaDUsIGg2IHtcbiAgZm9udC13ZWlnaHQ6IG5vcm1hbDsgfVxuXG5hcnRpY2xlLCBhc2lkZSwgY2FudmFzLCBkZXRhaWxzLCBmaWdjYXB0aW9uLCBmaWd1cmUsIGZvb3RlciwgaGVhZGVyLCBoZ3JvdXAsIG1lbnUsIG5hdiwgc2VjdGlvbiB7XG4gIGRpc3BsYXk6IGJsb2NrOyB9XG5cbiosICo6YmVmb3JlLCAqOmFmdGVyIHtcbiAgYm94LXNpemluZzogYm9yZGVyLWJveDsgfVxuXG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tXHJcblx0ICAgQkFTSUNTXHJcbi0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbi8qLS0gTm9ybWFsaXplIC0tKi9cbmVtIHtcbiAgZm9udC1zdHlsZTogaXRhbGljOyB9XG5cbnN0cm9uZyB7XG4gIGZvbnQtd2VpZ2h0OiBib2xkOyB9XG5cbnE6bGFuZyhlbikge1xuICBxdW90ZXM6ICcmIzgyMjA7JyAnJiM4MjIxOycgJyYjODIxNjsnICcmIzgyMTc7JzsgfVxuXG5oMSwgaDIsIGgzLCBoNCwgaDUsIGg2LCBwIHtcbiAgbWFyZ2luLWJvdHRvbTogMTVweDsgfVxuXG51bCB7XG4gIGxpc3Qtc3R5bGU6IG91dHNpZGUgZGlzYztcbiAgcGFkZGluZy1sZWZ0OiAyNHB4OyB9XG5cbm9sIHtcbiAgbGlzdC1zdHlsZTogb3V0c2lkZSBkZWNpbWFsO1xuICBwYWRkaW5nLWxlZnQ6IDI0cHg7IH1cblxuYSwgbGFiZWwsIGlucHV0W3R5cGU9YnV0dG9uXSwgaW5wdXRbdHlwZT1zdWJtaXRdLCBidXR0b24ge1xuICBjdXJzb3I6IHBvaW50ZXI7IH1cblxuZGVsIHtcbiAgdGV4dC1kZWNvcmF0aW9uOiBsaW5lLXRocm91Z2g7IH1cblxuYWJiclt0aXRsZV0sIGRmblt0aXRsZV0ge1xuICBib3JkZXItYm90dG9tOiAxcHggZG90dGVkO1xuICBjdXJzb3I6IGhlbHA7IH1cblxucHJlLCBjb2RlIHtcbiAgYmFja2dyb3VuZDogI2VlZTtcbiAgYm9yZGVyOiAxcHggc29saWQgI2NjYztcbiAgZm9udC1mYW1pbHk6IENvbnNvbGFzLCAnTHVjaWRhIENvbnNvbGUnLCAnQ291cmllciBOZXcnLCBzZXJpZjtcbiAgcGFkZGluZzogMiU7IH1cblxuaW5wdXQsIHNlbGVjdCwgdGV4dGFyZWEge1xuICBib3JkZXI6IDFweCBzb2xpZCAjY2NjO1xuICBmb250LWZhbWlseTogaW5oZXJpdDtcbiAgZm9udC1zaXplOiBpbmhlcml0O1xuICBwYWRkaW5nOiAzcHggNnB4OyB9XG5cbmlucHV0LCBzZWxlY3Qge1xuICB2ZXJ0aWNhbC1hbGlnbjogbWlkZGxlOyB9XG5cbnRleHRhcmVhIHtcbiAgb3ZlcmZsb3c6IGF1dG87IH1cblxuLyogcHJldmVudHMgc2Nyb2xsYmFyIGZyb20gc2hvd2luZyB1cCB3aGVuIHVubmVlZGVkIGluIElFICovXG5zbWFsbCwgc3VwLCBzdWIge1xuICBmb250LXNpemU6IDg1JTsgfVxuXG5zdXAge1xuICB2ZXJ0aWNhbC1hbGlnbjogc3VwZXI7IH1cblxuc3ViIHtcbiAgdmVydGljYWwtYWxpZ246IHN1YjsgfVxuXG50YWJsZSB7XG4gIG1hcmdpbjogMTBweCAwO1xuICBwYWRkaW5nOiAzcHg7IH1cblxudGhlYWQge1xuICBib3JkZXItYm90dG9tOiAycHggc29saWQgI2NjYzsgfVxuXG50aCB7XG4gIGZvbnQtd2VpZ2h0OiBib2xkOyB9XG5cbnRoLCB0ciwgdGQge1xuICBwYWRkaW5nOiA0cHggMTJweDsgfVxuXG50ciB7XG4gIGJvcmRlci1ib3R0b206IDFweCBzb2xpZCAjY2NjOyB9XG5cbi8qLS0gQ2xlYXJmaXggLS0qL1xuLmNsZWFyZml4OmJlZm9yZSxcbi5jbGVhcmZpeDphZnRlciB7XG4gIGNvbnRlbnQ6IFwiIFwiO1xuICBkaXNwbGF5OiB0YWJsZTsgfVxuXG4uY2xlYXJmaXg6YWZ0ZXIge1xuICBjbGVhcjogYm90aDsgfVxuXG4uaWU3IC5jbGVhcmZpeCB7XG4gIHpvb206IDE7IH1cblxuLyotLSBuZy1jbG9hazogcHJldmVudCBGT1VDIGJlZm9yZSBBbmd1bGFyIEphdmFTY3JpcHQgbG9hZHMgLS0qL1xuW25nXFw6Y2xvYWtdLFxuW25nLWNsb2FrXSxcbltkYXRhLW5nLWNsb2FrXSxcblt4LW5nLWNsb2FrXSxcbi5uZy1jbG9hayxcbi54LW5nLWNsb2FrIHtcbiAgZGlzcGxheTogbm9uZSAhaW1wb3J0YW50OyB9XG5cbi8qLS0gSW1hZ2UgUmVwbGFjZW1lbnQgLS0qL1xuLmlyIHtcbiAgb3ZlcmZsb3c6IGhpZGRlbjtcbiAgdGV4dC1pbmRlbnQ6IDIwMCU7XG4gIHdoaXRlLXNwYWNlOiBub3dyYXA7IH1cblxuLyotLSBJbmxpbmUgbGluayB0b3VjaCB0YXJnZXRzIC0tKi9cbi50b3VjaCBwIGEge1xuICBtYXJnaW46IDAgLS41ZW07XG4gIHBhZGRpbmc6IDAgLjVlbTsgfVxuXG4vKi0tIEZvcm1zIC0tKi9cbmlucHV0W3R5cGU9XCJ0ZXh0XCJdLFxuaW5wdXRbdHlwZT1cIm51bWJlclwiXSxcbmlucHV0W3R5cGU9XCJwYXNzd29yZFwiXSxcbnRleHRhcmVhIHtcbiAgZm9udC1zaXplOiAxNnB4O1xuICAvKiBmb3IgaU9TIHBob25lcyAqLyB9XG5cbmlucHV0Lm5nLWRpcnR5Lm5nLWludmFsaWQge1xuICBib3JkZXItY29sb3I6IHJnYmEoMTY5LCA2OCwgNjYsIDAuNik7XG4gIGJveC1zaGFkb3c6IDAgMCA2cHggcmdiYSgxNjksIDY4LCA2NiwgMC42KTsgfVxuXG5idXR0b24ge1xuICBib3JkZXItcmFkaXVzOiA0cHg7XG4gIGRpc3BsYXk6IGlubGluZS1ibG9jaztcbiAgZm9udC1zaXplOiBpbmhlcml0O1xuICBwYWRkaW5nOiA1cHggNHB4O1xuICB2ZXJ0aWNhbC1hbGlnbjogbWlkZGxlOyB9XG5cbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cclxuXHQgICBGT05UU1xyXG4tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG4vKiBGdWxsIEBmb250LWZhY2UgZGVjbGFyYXRpb24gKi9cbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cclxuXHQgUFJFU0VOVEFUSU9OXHJcbi0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbmJvZHkge1xuICBiYWNrZ3JvdW5kOiAjZWVlO1xuICBjb2xvcjogIzMzMztcbiAgZm9udDogbm9ybWFsIDE2cHgvMS4yICdIZWx2ZXRpY2EgTmV1ZScsIEhlbHZldGljYSwgQXJpYWwsIFZlcmRhbmEsIHNhbnMtc2VyaWY7IH1cblxuLmxheW91dC1jYW52YXMge1xuICBiYWNrZ3JvdW5kOiAjZmZmOyB9XG5cbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSBDb250ZW50IC0tKi9cbi5jb250ZW50LXdyYXBwZXIge1xuICBwYWRkaW5nOiAzJTsgfVxuICBAbWVkaWEgc2NyZWVuIGFuZCAobWluLXdpZHRoOiA3NjhweCkge1xuICAgIC5jb250ZW50LXdyYXBwZXIge1xuICAgICAgcGFkZGluZzogMS41JSAzJTsgfSB9XG5cbi5jb250ZW50LWhlYWRpbmcge1xuICBmb250LXNpemU6IDM2cHg7XG4gIG1hcmdpbjogLTEuNSUgMCAyMHB4OyB9XG4gIEBtZWRpYSBzY3JlZW4gYW5kIChtaW4td2lkdGg6IDc2OHB4KSB7XG4gICAgLmNvbnRlbnQtaGVhZGluZyB7XG4gICAgICBtYXJnaW4tdG9wOiAwO1xuICAgICAgdGV4dC1hbGlnbjogY2VudGVyOyB9IH1cblxuLyotLS0tLS0tLS0tLS0tLS0tLS0tLVxyXG4gTEFZT1VUIEZVTkNUSU9OQUxJVFlcclxuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuYm9keSB7XG4gIG1pbi13aWR0aDogMzIwcHg7IH1cblxuLmxheW91dC1vdmVyZmxvdyB7XG4gIG92ZXJmbG93OiBoaWRkZW47XG4gIC8qIG5lY2Vzc2FyeSB0byBoYW5kbGUgb2ZmY2FudmFzIHNjcm9sbGJhciBiZWhhdmlvciAqLyB9XG5cbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSBPZmYtY2FudmFzIEZ1bmN0aW9uYWxpdHkgLS0qL1xuLyotLSBDb250ZW50IGNhbnZhcyB3cmFwcGVyIC0tKi9cbi5sYXlvdXQtY2FudmFzIHtcbiAgYmFja2ZhY2UtdmlzaWJpbGl0eTogaGlkZGVuO1xuICBwb3NpdGlvbjogcmVsYXRpdmU7XG4gIGxlZnQ6IDA7XG4gIHdpZHRoOiAxMDAlOyB9XG4gIEBtZWRpYSBzY3JlZW4gYW5kIChtaW4td2lkdGg6IDc2OHB4KSB7XG4gICAgLmxheW91dC1jYW52YXMge1xuICAgICAgdHJhbnNpdGlvbjogbm9uZTtcbiAgICAgIHRyYW5zZm9ybTogbm9uZTsgfSB9XG4gIC5jc3N0cmFuc2Zvcm1zM2QgLmxheW91dC1jYW52YXMge1xuICAgIHRyYW5zaXRpb246IHRyYW5zZm9ybSAyNTBtcyBlYXNlO1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlM2QoMCwgMCwgMCk7IH1cbiAgLmNzc3RyYW5zZm9ybXMzZCAubmF2LW9wZW4gLmxheW91dC1jYW52YXMge1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlM2QoMjcwcHgsIDAsIDApOyB9XG4gIC5uby1jc3N0cmFuc2Zvcm1zM2QgLm5hdi1vcGVuIC5sYXlvdXQtY2FudmFzIHtcbiAgICBsZWZ0OiAyNzBweDsgfVxuXG4vKi0tIEhlYWRlciAtLSovXG5AbWVkaWEgc2NyZWVuIGFuZCAobWluLXdpZHRoOiA3NjhweCkge1xuICAuaGVhZGVyLW1vYmlsZS1wYWdlIHtcbiAgICBkaXNwbGF5OiBub25lOyB9IH1cblxuLyotLSBOYXZpZ2F0aW9uIC0tKi9cbi5oZWFkZXIgLm5hdiB7XG4gIGJhY2tmYWNlLXZpc2liaWxpdHk6IGhpZGRlbjtcbiAgZGlzcGxheTogbm9uZTtcbiAgLyogZGVhbCB3aXRoIEZPVUMgKi9cbiAgaGVpZ2h0OiAxMDAlO1xuICBvdmVyZmxvdy15OiBhdXRvO1xuICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gIHRvcDogMDtcbiAgd2lkdGg6IDI3MHB4OyB9XG4gIEBtZWRpYSBzY3JlZW4gYW5kIChtaW4td2lkdGg6IDc2OHB4KSB7XG4gICAgLmhlYWRlciAubmF2IHtcbiAgICAgIGRpc3BsYXk6IGJsb2NrO1xuICAgICAgcG9zaXRpb246IHJlbGF0aXZlO1xuICAgICAgd2lkdGg6IDEwMCU7IH0gfVxuICAubmF2LWNsb3NlZCAuaGVhZGVyIC5uYXYsXG4gIC5uYXYtb3BlbiAuaGVhZGVyIC5uYXYge1xuICAgIGRpc3BsYXk6IGJsb2NrO1xuICAgIC8qIGRlYWwgd2l0aCBGT1VDICovIH1cbiAgLmNzc3RyYW5zZm9ybXMzZCAuaGVhZGVyIC5uYXYge1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlM2QoLTEwMCUsIDAsIDApOyB9XG4gICAgQG1lZGlhIHNjcmVlbiBhbmQgKG1pbi13aWR0aDogNzY4cHgpIHtcbiAgICAgIC5jc3N0cmFuc2Zvcm1zM2QgLmhlYWRlciAubmF2IHtcbiAgICAgICAgdHJhbnNmb3JtOiBub25lOyB9IH1cbiAgLm5vLWNzc3RyYW5zZm9ybXMzZCAubmF2LWNsb3NlZCAuaGVhZGVyIC5uYXYge1xuICAgIGxlZnQ6IC0xMDAlOyB9XG4gIC5uby1jc3N0cmFuc2Zvcm1zM2QgLm5hdi1vcGVuIC5oZWFkZXIgLm5hdiB7XG4gICAgbGVmdDogLTI3MHB4OyB9XG4gICAgQG1lZGlhIHNjcmVlbiBhbmQgKG1pbi13aWR0aDogNzY4cHgpIHtcbiAgICAgIC5uby1jc3N0cmFuc2Zvcm1zM2QgLm5hdi1vcGVuIC5oZWFkZXIgLm5hdiB7XG4gICAgICAgIGxlZnQ6IDA7IH0gfVxuXG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tXHJcblx0ICAgUFJJTlRcclxuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuQG1lZGlhIHByaW50IHtcbiAgKiB7XG4gICAgYmFja2dyb3VuZDogI2ZmZjsgfVxuICBib2R5IHtcbiAgICBjb2xvcjogIzAwMDtcbiAgICBmb250OiBub3JtYWwgMTZweC8xLjQgR2VvcmdpYSwgJ1RpbWVzIE5ldyBSb21hbicsIHNlcmlmOyB9XG4gIC8qLS0gSGlkZGVuIEVsZW1lbnRzIC0tKi9cbiAgLmhlYWRlcixcbiAgLmZvb3RlciB7XG4gICAgZGlzcGxheTogbm9uZTsgfVxuICAvKi0tIFNob3cgbGluayBVUkxzIC0tKi9cbiAgYTpsaW5rLFxuICBhOnZpc2l0ZWQge1xuICAgIGNvbG9yOiBibHVlO1xuICAgIHRleHQtZGVjb3JhdGlvbjogdW5kZXJsaW5lOyB9XG4gIGE6bGluazphZnRlcixcbiAgYTp2aXNpdGVkOmFmdGVyIHtcbiAgICBjb250ZW50OiBcIiBbXCIgYXR0cihocmVmKSBcIl0gXCI7XG4gICAgZm9udC1zaXplOiA3NSU7IH0gfVxuXG4vKi0tIE1vZHVsZXMgLS0qL1xuLyotLS0tLS0tLS0tLS0tLS0tLS0tLVxyXG5cdCAgICBIRUFERVJcclxuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuLmhlYWRlci1tb2JpbGUtcGFnZSB7XG4gIGJhY2tncm91bmQ6ICM1NTU7XG4gIGNvbG9yOiAjZmZmO1xuICBoZWlnaHQ6IDUwcHg7XG4gIG1hcmdpbi1ib3R0b206IDEwcHg7XG4gIHBvc2l0aW9uOiByZWxhdGl2ZTsgfVxuICAuaGVhZGVyLW1vYmlsZS1wYWdlLXNpdGVUaXRsZSB7XG4gICAgZm9udC1zaXplOiAzMHB4O1xuICAgIGxpbmUtaGVpZ2h0OiA1MHB4O1xuICAgIG1hcmdpbjogMDtcbiAgICBwYWRkaW5nOiAwIDAgMCA1MHB4O1xuICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICB0b3A6IDA7XG4gICAgdGV4dC1hbGlnbjogY2VudGVyO1xuICAgIHdpZHRoOiAxMDAlOyB9XG5cbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cclxuXHQgTkFWSUdBVElPTlxyXG4tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gSGFtYnVyZ2VyIG1lbnUgdG9nZ2xlIC0tKi9cbi50b2dnbGUtb2ZmY2FudmFzIHtcbiAgYmFja2dyb3VuZDogIzU1NTtcbiAgYm9yZGVyLXJpZ2h0OiAxcHggc29saWQgcmdiYSgyNTUsIDI1NSwgMjU1LCAwLjUpO1xuICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7XG4gIGhlaWdodDogNTBweDtcbiAgcGFkZGluZzogMjMuNXB4IDEzcHg7XG4gIHBvc2l0aW9uOiByZWxhdGl2ZTtcbiAgdGV4dC1hbGlnbjogY2VudGVyO1xuICB3aWR0aDogNTBweDtcbiAgei1pbmRleDogMTAwOyB9XG4gIC50b2dnbGUtb2ZmY2FudmFzIHNwYW4sXG4gIC50b2dnbGUtb2ZmY2FudmFzIHNwYW46YmVmb3JlLFxuICAudG9nZ2xlLW9mZmNhbnZhcyBzcGFuOmFmdGVyIHtcbiAgICBiYWNrZ3JvdW5kOiAjZmZmO1xuICAgIGJvcmRlci1yYWRpdXM6IDFweDtcbiAgICBjb250ZW50OiAnJztcbiAgICBkaXNwbGF5OiBibG9jaztcbiAgICBoZWlnaHQ6IDNweDtcbiAgICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gICAgdHJhbnNpdGlvbjogYWxsIDI1MG1zIGVhc2UtaW4tb3V0O1xuICAgIHdpZHRoOiAyNHB4OyB9XG4gIC50b2dnbGUtb2ZmY2FudmFzIHNwYW46YmVmb3JlIHtcbiAgICB0b3A6IC05cHg7IH1cbiAgLnRvZ2dsZS1vZmZjYW52YXMgc3BhbjphZnRlciB7XG4gICAgYm90dG9tOiAtOXB4OyB9XG4gIC5uYXYtb3BlbiAudG9nZ2xlLW9mZmNhbnZhcyBzcGFuIHtcbiAgICBiYWNrZ3JvdW5kOiB0cmFuc3BhcmVudDsgfVxuICAgIC5uYXYtb3BlbiAudG9nZ2xlLW9mZmNhbnZhcyBzcGFuOmJlZm9yZSwgLm5hdi1vcGVuIC50b2dnbGUtb2ZmY2FudmFzIHNwYW46YWZ0ZXIge1xuICAgICAgdG9wOiAwOyB9XG4gICAgLm5hdi1vcGVuIC50b2dnbGUtb2ZmY2FudmFzIHNwYW46YmVmb3JlIHtcbiAgICAgIHRyYW5zZm9ybTogcm90YXRlKDQ1ZGVnKTsgfVxuICAgIC5uYXYtb3BlbiAudG9nZ2xlLW9mZmNhbnZhcyBzcGFuOmFmdGVyIHtcbiAgICAgIHRyYW5zZm9ybTogcm90YXRlKC00NWRlZyk7IH1cblxuLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIE5hdmlnYXRpb24gLS0qL1xuLmhlYWRlciAubmF2IHtcbiAgYm94LXNoYWRvdzogaW5zZXQgLThweCAwIDhweCAtNnB4IHJnYmEoMCwgMCwgMCwgMC4yKTtcbiAgcGFkZGluZzogMyU7IH1cbiAgQG1lZGlhIHNjcmVlbiBhbmQgKG1pbi13aWR0aDogNzY4cHgpIHtcbiAgICAuaGVhZGVyIC5uYXYge1xuICAgICAgYmFja2dyb3VuZDogIzU1NTtcbiAgICAgIGJveC1zaGFkb3c6IG5vbmU7XG4gICAgICBwYWRkaW5nOiAxLjUlIDMlOyB9IH1cbiAgLmhlYWRlciAubmF2IC5hY3RpdmUgLm5hdi1saXN0LWl0ZW0tdGV4dCB7XG4gICAgZm9udC13ZWlnaHQ6IGJvbGQ7IH1cbiAgICBAbWVkaWEgc2NyZWVuIGFuZCAobWluLXdpZHRoOiA3NjhweCkge1xuICAgICAgLmhlYWRlciAubmF2IC5hY3RpdmUgLm5hdi1saXN0LWl0ZW0tdGV4dCB7XG4gICAgICAgIGJvcmRlci1ib3R0b206IDFweCBzb2xpZCByZ2JhKDI1NSwgMjU1LCAyNTUsIDAuNSk7IH0gfVxuICAuaGVhZGVyIC5uYXYtbGlzdCB7XG4gICAgbGlzdC1zdHlsZTogbm9uZTtcbiAgICBtYXJnaW4tYm90dG9tOiAwO1xuICAgIHBhZGRpbmctbGVmdDogMDsgfVxuICAgIC5oZWFkZXIgLm5hdi1saXN0IGEge1xuICAgICAgZGlzcGxheTogYmxvY2s7XG4gICAgICBwYWRkaW5nOiA2cHg7IH1cbiAgICAgIC5oZWFkZXIgLm5hdi1saXN0IGE6aG92ZXIsIC5oZWFkZXIgLm5hdi1saXN0IGE6YWN0aXZlLCAuaGVhZGVyIC5uYXYtbGlzdCBhOmZvY3VzIHtcbiAgICAgICAgdGV4dC1kZWNvcmF0aW9uOiBub25lOyB9XG4gICAgQG1lZGlhIHNjcmVlbiBhbmQgKG1pbi13aWR0aDogNzY4cHgpIHtcbiAgICAgIC5oZWFkZXIgLm5hdi1saXN0IHtcbiAgICAgICAgZGlzcGxheTogZmxleDtcbiAgICAgICAgZmxleC13cmFwOiB3cmFwO1xuICAgICAgICBqdXN0aWZ5LWNvbnRlbnQ6IGNlbnRlcjtcbiAgICAgICAgcGFkZGluZzogMDtcbiAgICAgICAgd2lkdGg6IDEwMCU7IH1cbiAgICAgICAgLmhlYWRlciAubmF2LWxpc3QgYSxcbiAgICAgICAgLmhlYWRlciAubmF2LWxpc3QgYTpob3ZlcixcbiAgICAgICAgLmhlYWRlciAubmF2LWxpc3QgYTphY3RpdmUsXG4gICAgICAgIC5oZWFkZXIgLm5hdi1saXN0IGE6Zm9jdXMge1xuICAgICAgICAgIGNvbG9yOiAjZmZmOyB9XG4gICAgICAgIC5oZWFkZXIgLm5hdi1saXN0IGxpIHtcbiAgICAgICAgICBwYWRkaW5nOiAwIDIwcHg7IH0gfVxuXG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tXHJcblx0ICAgIEZPT1RFUlxyXG4tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG4uZm9vdGVyIHtcbiAgcGFkZGluZzogMyU7XG4gIHRleHQtYWxpZ246IGNlbnRlcjsgfVxuICBAbWVkaWEgc2NyZWVuIGFuZCAobWluLXdpZHRoOiA3NjhweCkge1xuICAgIC5mb290ZXIge1xuICAgICAgcGFkZGluZzogMS41JSAzJTsgfSB9XG5cbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cclxuXHQgICAgTE9BRElOR1xyXG4tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG4ubG9hZGluZy13cmFwcGVyIHtcbiAgaGVpZ2h0OiAxMDAlO1xuICBwb3NpdGlvbjogZml4ZWQ7XG4gIHRvcDogMDtcbiAgbGVmdDogMDtcbiAgd2lkdGg6IDEwMCU7XG4gIHotaW5kZXg6IDk5OTsgfVxuXG4ubG9hZGluZy1vdmVybGF5IHtcbiAgYmFja2dyb3VuZDogcmdiYSgyNTUsIDI1NSwgMjU1LCAwLjkpO1xuICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gIHRvcDogMDtcbiAgYm90dG9tOiAwO1xuICB3aWR0aDogMTAwJTsgfVxuXG4ubG9hZGluZy1zcGlubmVyIHtcbiAgbWFyZ2luOiAtMTZweCAwIDAgLTE2cHg7XG4gIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgdG9wOiA1MCU7XG4gIGxlZnQ6IDUwJTsgfVxuXG4vKi0tIFBhZ2VzIC0tKi9cbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cclxuXHQgICAgNDA0XHJcbi0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbiIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cclxuXHQgICBGT05UU1xyXG4tLS0tLS0tLS0tLS0tLS0tLS0tLSovXHJcblxyXG4vKiBGdWxsIEBmb250LWZhY2UgZGVjbGFyYXRpb24gKi9cclxuXHJcbkBtZWRpYSBvbmx5IHNjcmVlbiBhbmQgKG1heC13aWR0aDogMzIwcHgpLCBzY3JlZW4gYW5kIChtYXgtZGV2aWNlLXdpZHRoOiA3MjBweCkgYW5kIChvcmllbnRhdGlvbjpwb3J0cmFpdCksIHNjcmVlbiBhbmQgKG1heC1kZXZpY2Utd2lkdGg6IDEyODBweCkgYW5kIChvcmllbnRhdGlvbjpsYW5kc2NhcGUpIHtcclxuXHQvKiBmb3IgbW9iaWxlIGRldmljZXMsIG9ubHkgbG9hZCBTVkcgZm9udFxyXG5cdHNlZTogaHR0cDovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy8yMDg5MDQ4OS9mb250LWZhY2UtZGVjbGFyYXRpb25zLWRvbnQtd29yay1pbi1hbmRyb2lkLTQtMy1pbnRlcm5ldC1icm93c2VyXHJcblxyXG5cdEBmb250LWZhY2Uge1xyXG5cdFx0Zm9udC1mYW1pbHk6ICdGb250TmFtZSc7XHJcblx0XHRzcmM6IHVybCgnLi4vZm9udHMvRm9udE5hbWUuc3ZnI0ZvbnROYW1lJykgZm9ybWF0KCdzdmcnKTtcclxuXHRcdGZvbnQtd2VpZ2h0OiBub3JtYWw7XHJcblx0XHRmb250LXN0eWxlOiBub3JtYWw7XHJcblx0fSAqL1xyXG59IiwiLyotLS0tLS0tLS0tLS0tLS0tLS0tLVxyXG5cdCBQUkVTRU5UQVRJT05cclxuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xyXG5cclxuYm9keSB7XHJcblx0YmFja2dyb3VuZDogJGNvbG9yLWJnLWxpZ2h0O1xyXG5cdGNvbG9yOiAkY29sb3ItdGV4dDtcclxuXHRmb250OiBub3JtYWwgMTZweC8xLjIgJ0hlbHZldGljYSBOZXVlJywgSGVsdmV0aWNhLCBBcmlhbCwgVmVyZGFuYSwgc2Fucy1zZXJpZjtcclxufVxyXG4ubGF5b3V0LWNhbnZhcyB7XHJcblx0YmFja2dyb3VuZDogJGNvbG9yLWJnO1xyXG59XHJcblxyXG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gQ29udGVudCAtLSovXHJcblxyXG4uY29udGVudC13cmFwcGVyIHtcclxuXHRwYWRkaW5nOiAkcGFkZGluZy1zY3JlZW4tc21hbGw7XHJcblxyXG5cdEBpbmNsdWRlIG1xKCRtcS1sYXJnZSkge1xyXG5cdFx0cGFkZGluZzogJHBhZGRpbmctc2NyZWVuLWxhcmdlO1xyXG5cdH1cclxufVxyXG4uY29udGVudC1oZWFkaW5nIHtcclxuXHRmb250LXNpemU6IDM2cHg7XHJcblx0bWFyZ2luOiAtMS41JSAwIDIwcHg7XHJcblxyXG5cdEBpbmNsdWRlIG1xKCRtcS1sYXJnZSkge1xyXG5cdFx0bWFyZ2luLXRvcDogMDtcclxuXHRcdHRleHQtYWxpZ246IGNlbnRlcjtcclxuXHR9XHJcbn0iLCIvKi0tLS0tLS0tLS0tLS0tLS0tLS0tXHJcbiBMQVlPVVQgRlVOQ1RJT05BTElUWVxyXG4tLS0tLS0tLS0tLS0tLS0tLS0tLSovXHJcblxyXG5ib2R5IHtcclxuXHRtaW4td2lkdGg6IDMyMHB4O1xyXG59XHJcbi5sYXlvdXQtb3ZlcmZsb3cge1xyXG5cdG92ZXJmbG93OiBoaWRkZW47XHQvKiBuZWNlc3NhcnkgdG8gaGFuZGxlIG9mZmNhbnZhcyBzY3JvbGxiYXIgYmVoYXZpb3IgKi9cclxufVxyXG5cclxuLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIE9mZi1jYW52YXMgRnVuY3Rpb25hbGl0eSAtLSovXHJcblxyXG4vKi0tIENvbnRlbnQgY2FudmFzIHdyYXBwZXIgLS0qL1xyXG5cclxuLmxheW91dC1jYW52YXMge1xyXG5cdGJhY2tmYWNlLXZpc2liaWxpdHk6IGhpZGRlbjtcclxuXHRwb3NpdGlvbjogcmVsYXRpdmU7XHJcblx0bGVmdDogMDtcclxuXHR3aWR0aDogMTAwJTtcclxuXHJcblx0QGluY2x1ZGUgbXEoJG1xLWxhcmdlKSB7XHJcblx0XHR0cmFuc2l0aW9uOiBub25lO1xyXG5cdFx0dHJhbnNmb3JtOiBub25lO1xyXG5cdH1cclxuXHQuY3NzdHJhbnNmb3JtczNkICYge1xyXG5cdFx0dHJhbnNpdGlvbjogdHJhbnNmb3JtIDI1MG1zIGVhc2U7XHJcblx0XHR0cmFuc2Zvcm06IHRyYW5zbGF0ZTNkKDAsMCwwKTtcclxuXHR9XHJcblx0LmNzc3RyYW5zZm9ybXMzZCAubmF2LW9wZW4gJiB7XHJcblx0XHR0cmFuc2Zvcm06IHRyYW5zbGF0ZTNkKDI3MHB4LDAsMCk7XHJcblx0fVxyXG5cdC5uby1jc3N0cmFuc2Zvcm1zM2QgLm5hdi1vcGVuICYge1xyXG5cdFx0bGVmdDogMjcwcHg7XHJcblx0fVxyXG59XHJcblxyXG4vKi0tIEhlYWRlciAtLSovXHJcblxyXG4uaGVhZGVyLW1vYmlsZS1wYWdlIHtcclxuXHRAaW5jbHVkZSBtcSgkbXEtbGFyZ2UpIHtcclxuXHRcdGRpc3BsYXk6IG5vbmU7XHJcblx0fVxyXG59XHJcblxyXG4vKi0tIE5hdmlnYXRpb24gLS0qL1xyXG5cclxuLmhlYWRlciAubmF2IHtcclxuXHRiYWNrZmFjZS12aXNpYmlsaXR5OiBoaWRkZW47XHJcblx0ZGlzcGxheTogbm9uZTtcdC8qIGRlYWwgd2l0aCBGT1VDICovXHJcblx0aGVpZ2h0OiAxMDAlO1xyXG5cdG92ZXJmbG93LXk6IGF1dG87XHJcblx0cG9zaXRpb246IGFic29sdXRlO1xyXG5cdHRvcDogMDtcclxuXHR3aWR0aDogMjcwcHg7XHJcblxyXG5cdEBpbmNsdWRlIG1xKCRtcS1sYXJnZSkge1xyXG5cdFx0ZGlzcGxheTogYmxvY2s7XHJcblx0XHRwb3NpdGlvbjogcmVsYXRpdmU7XHJcblx0XHR3aWR0aDogMTAwJTtcclxuXHR9XHJcblx0Lm5hdi1jbG9zZWQgJixcclxuXHQubmF2LW9wZW4gJiB7XHJcblx0XHRkaXNwbGF5OiBibG9jaztcdC8qIGRlYWwgd2l0aCBGT1VDICovXHJcblx0fVxyXG5cdC5jc3N0cmFuc2Zvcm1zM2QgJiB7XHJcblx0XHR0cmFuc2Zvcm06IHRyYW5zbGF0ZTNkKC0xMDAlLDAsMCk7XHJcblxyXG5cdFx0QGluY2x1ZGUgbXEoJG1xLWxhcmdlKSB7XHJcblx0XHRcdHRyYW5zZm9ybTogbm9uZTtcclxuXHRcdH1cclxuXHR9XHJcblx0Lm5vLWNzc3RyYW5zZm9ybXMzZCAubmF2LWNsb3NlZCAmIHtcclxuXHRcdGxlZnQ6IC0xMDAlO1xyXG5cdH1cclxuXHQubm8tY3NzdHJhbnNmb3JtczNkIC5uYXYtb3BlbiAmIHtcclxuXHRcdGxlZnQ6IC0yNzBweDtcclxuXHJcblx0XHRAaW5jbHVkZSBtcSgkbXEtbGFyZ2UpIHtcclxuXHRcdFx0bGVmdDogMDtcclxuXHRcdH1cclxuXHR9XHJcbn0iLCIvKi0tLS0tLS0tLS0tLS0tLS0tLS0tXHJcblx0ICAgUFJJTlRcclxuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xyXG5cclxuQG1lZGlhIHByaW50IHtcclxuXHJcblx0KiB7XHJcblx0XHRiYWNrZ3JvdW5kOiAjZmZmO1xyXG5cdH1cclxuXHRib2R5IHtcclxuXHRcdGNvbG9yOiAjMDAwO1xyXG5cdFx0Zm9udDogbm9ybWFsIDE2cHgvMS40IEdlb3JnaWEsICdUaW1lcyBOZXcgUm9tYW4nLCBzZXJpZjtcclxuXHR9XHJcblxyXG5cdC8qLS0gSGlkZGVuIEVsZW1lbnRzIC0tKi9cclxuXHJcblx0LmhlYWRlcixcclxuXHQuZm9vdGVyIHtcclxuXHRcdGRpc3BsYXk6IG5vbmU7XHJcblx0fVxyXG5cclxuXHQvKi0tIFNob3cgbGluayBVUkxzIC0tKi9cclxuXHJcblx0YTpsaW5rLFxyXG5cdGE6dmlzaXRlZCB7XHJcblx0XHRjb2xvcjogYmx1ZTtcclxuXHRcdHRleHQtZGVjb3JhdGlvbjogdW5kZXJsaW5lO1xyXG5cdH1cclxuXHRhOmxpbms6YWZ0ZXIsXHJcblx0YTp2aXNpdGVkOmFmdGVyIHtcclxuXHRcdGNvbnRlbnQ6XCIgW1wiIGF0dHIoaHJlZikgXCJdIFwiO1xyXG5cdFx0Zm9udC1zaXplOiA3NSU7XHJcblx0fVxyXG5cclxufSIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cclxuXHQgICAgSEVBREVSXHJcbi0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cclxuXHJcbi5oZWFkZXItbW9iaWxlLXBhZ2Uge1xyXG5cdGJhY2tncm91bmQ6ICRjb2xvci1uYXYtYmc7XHJcblx0Y29sb3I6ICRjb2xvci13aGl0ZTtcclxuXHRoZWlnaHQ6IDUwcHg7XHJcblx0bWFyZ2luLWJvdHRvbTogMTBweDtcclxuXHRwb3NpdGlvbjogcmVsYXRpdmU7XHJcblxyXG5cdCYtc2l0ZVRpdGxlIHtcclxuXHRcdGZvbnQtc2l6ZTogMzBweDtcclxuXHRcdGxpbmUtaGVpZ2h0OiA1MHB4O1xyXG5cdFx0bWFyZ2luOiAwO1xyXG5cdFx0cGFkZGluZzogMCAwIDAgNTBweDtcclxuXHRcdHBvc2l0aW9uOiBhYnNvbHV0ZTtcclxuXHRcdHRvcDogMDtcclxuXHRcdHRleHQtYWxpZ246IGNlbnRlcjtcclxuXHRcdHdpZHRoOiAxMDAlO1xyXG5cdH1cclxufSIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cclxuXHQgTkFWSUdBVElPTlxyXG4tLS0tLS0tLS0tLS0tLS0tLS0tLSovXHJcblxyXG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gSGFtYnVyZ2VyIG1lbnUgdG9nZ2xlIC0tKi9cclxuXHJcbi50b2dnbGUtb2ZmY2FudmFzIHtcclxuXHRiYWNrZ3JvdW5kOiAkY29sb3ItbmF2LWJnO1xyXG5cdGJvcmRlci1yaWdodDogMXB4IHNvbGlkICRjb2xvci1kaXNhYmxlZC1yZ2JhO1xyXG5cdGRpc3BsYXk6IGlubGluZS1ibG9jaztcclxuXHRoZWlnaHQ6IDUwcHg7XHJcblx0cGFkZGluZzogMjMuNXB4IDEzcHg7XHJcblx0cG9zaXRpb246IHJlbGF0aXZlO1xyXG5cdHRleHQtYWxpZ246IGNlbnRlcjtcclxuXHR3aWR0aDogNTBweDtcclxuXHR6LWluZGV4OiAxMDA7XHJcblxyXG5cdHNwYW4sXHJcblx0c3BhbjpiZWZvcmUsXHJcblx0c3BhbjphZnRlciB7XHJcblx0XHRiYWNrZ3JvdW5kOiAkY29sb3Itd2hpdGU7XHJcblx0XHRib3JkZXItcmFkaXVzOiAxcHg7XHJcblx0XHRjb250ZW50OiAnJztcclxuXHRcdGRpc3BsYXk6IGJsb2NrO1xyXG5cdFx0aGVpZ2h0OiAzcHg7XHJcblx0XHRwb3NpdGlvbjogYWJzb2x1dGU7XHJcblx0XHR0cmFuc2l0aW9uOiBhbGwgMjUwbXMgZWFzZS1pbi1vdXQ7XHJcblx0XHR3aWR0aDogMjRweDtcclxuXHR9XHJcblx0c3BhbiB7XHJcblx0XHQmOmJlZm9yZSB7XHJcblx0XHRcdHRvcDogLTlweDtcclxuXHRcdH1cclxuXHRcdCY6YWZ0ZXIge1xyXG5cdFx0XHRib3R0b206IC05cHg7XHJcblx0XHR9XHJcblx0fVxyXG5cdC5uYXYtb3BlbiAmIHtcclxuXHRcdHNwYW4ge1xyXG5cdFx0XHRiYWNrZ3JvdW5kOiB0cmFuc3BhcmVudDtcclxuXHJcblx0XHRcdCY6YmVmb3JlLFxyXG5cdFx0XHQmOmFmdGVyIHtcclxuXHRcdFx0XHR0b3A6IDA7XHJcblx0XHRcdH1cclxuXHRcdFx0JjpiZWZvcmUge1xyXG5cdFx0XHRcdHRyYW5zZm9ybTogcm90YXRlKDQ1ZGVnKTtcclxuXHRcdFx0fVxyXG5cdFx0XHQmOmFmdGVyIHtcclxuXHRcdFx0XHR0cmFuc2Zvcm06IHJvdGF0ZSgtNDVkZWcpO1xyXG5cdFx0XHR9XHJcblx0XHR9XHJcblx0fVxyXG59XHJcblxyXG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gTmF2aWdhdGlvbiAtLSovXHJcblxyXG4uaGVhZGVyIC5uYXYge1xyXG5cdGJveC1zaGFkb3c6IGluc2V0IC04cHggMCA4cHggLTZweCByZ2JhKDAsMCwwLDAuMik7XHJcblx0cGFkZGluZzogJHBhZGRpbmctc2NyZWVuLXNtYWxsO1xyXG5cclxuXHRAaW5jbHVkZSBtcSgkbXEtbGFyZ2UpIHtcclxuXHRcdGJhY2tncm91bmQ6ICRjb2xvci1uYXYtYmc7XHJcblx0XHRib3gtc2hhZG93OiBub25lO1xyXG5cdFx0cGFkZGluZzogJHBhZGRpbmctc2NyZWVuLWxhcmdlO1xyXG5cdH1cclxuXHJcblx0LmFjdGl2ZSAubmF2LWxpc3QtaXRlbS10ZXh0IHtcclxuXHRcdGZvbnQtd2VpZ2h0OiBib2xkO1xyXG5cclxuXHRcdEBpbmNsdWRlIG1xKCRtcS1sYXJnZSkge1xyXG5cdFx0XHRib3JkZXItYm90dG9tOiAxcHggc29saWQgJGNvbG9yLWRpc2FibGVkLXJnYmE7XHJcblx0XHR9XHJcblx0fVxyXG5cdCYtbGlzdCB7XHJcblx0XHRsaXN0LXN0eWxlOiBub25lO1xyXG5cdFx0bWFyZ2luLWJvdHRvbTogMDtcclxuXHRcdHBhZGRpbmctbGVmdDogMDtcclxuXHJcblx0XHRhIHtcclxuXHRcdFx0ZGlzcGxheTogYmxvY2s7XHJcblx0XHRcdHBhZGRpbmc6IDZweDtcclxuXHJcblx0XHRcdCY6aG92ZXIsXHJcblx0XHRcdCY6YWN0aXZlLFxyXG5cdFx0XHQmOmZvY3VzIHtcclxuXHRcdFx0XHR0ZXh0LWRlY29yYXRpb246IG5vbmU7XHJcblx0XHRcdH1cclxuXHRcdH1cclxuXHJcblx0XHRAaW5jbHVkZSBtcSgkbXEtbGFyZ2UpIHtcclxuXHRcdFx0ZGlzcGxheTogZmxleDtcclxuXHRcdFx0ZmxleC13cmFwOiB3cmFwO1xyXG5cdFx0XHRqdXN0aWZ5LWNvbnRlbnQ6IGNlbnRlcjtcclxuXHRcdFx0cGFkZGluZzogMDtcclxuXHRcdFx0d2lkdGg6IDEwMCU7XHJcblxyXG5cdFx0XHRhLFxyXG5cdFx0XHRhOmhvdmVyLFxyXG5cdFx0XHRhOmFjdGl2ZSxcclxuXHRcdFx0YTpmb2N1cyB7XHJcblx0XHRcdFx0Y29sb3I6ICRjb2xvci13aGl0ZTtcclxuXHRcdFx0fVxyXG5cdFx0XHRsaSB7XHJcblx0XHRcdFx0cGFkZGluZzogMCAyMHB4O1xyXG5cdFx0XHR9XHJcblx0XHR9XHJcblx0fVxyXG59IiwiLyotLS0tLS0tLS0tLS0tLS0tLS0tLVxyXG5cdCAgICBGT09URVJcclxuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xyXG5cclxuLmZvb3RlciB7XHJcblx0cGFkZGluZzogJHBhZGRpbmctc2NyZWVuLXNtYWxsO1xyXG5cdHRleHQtYWxpZ246IGNlbnRlcjtcclxuXHJcblx0QGluY2x1ZGUgbXEoJG1xLWxhcmdlKSB7XHJcblx0XHRwYWRkaW5nOiAkcGFkZGluZy1zY3JlZW4tbGFyZ2U7XHJcblx0fVxyXG59IiwiLyotLS0tLS0tLS0tLS0tLS0tLS0tLVxyXG5cdCAgICBMT0FESU5HXHJcbi0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cclxuXHJcbi5sb2FkaW5nIHtcclxuXHQmLXdyYXBwZXIge1xyXG5cdFx0aGVpZ2h0OiAxMDAlO1xyXG5cdFx0cG9zaXRpb246IGZpeGVkO1xyXG5cdFx0XHR0b3A6IDA7IGxlZnQ6IDA7XHJcblx0XHR3aWR0aDogMTAwJTtcclxuXHRcdHotaW5kZXg6IDk5OTtcclxuXHR9XHJcblx0Ji1vdmVybGF5IHtcclxuXHRcdGJhY2tncm91bmQ6IHJnYmEoMjU1LDI1NSwyNTUsLjkpO1xyXG5cdFx0cG9zaXRpb246IGFic29sdXRlO1xyXG5cdFx0XHR0b3A6IDA7IGJvdHRvbTogMDtcclxuXHRcdHdpZHRoOiAxMDAlO1xyXG5cdH1cclxuXHQmLXNwaW5uZXIge1xyXG5cdFx0bWFyZ2luOiAtMTZweCAwIDAgLTE2cHg7XHJcblx0XHRwb3NpdGlvbjogYWJzb2x1dGU7XHJcblx0XHRcdHRvcDogNTAlOyBsZWZ0OiA1MCU7XHJcblx0fVxyXG59IiwiLyotLS0tLS0tLS0tLS0tLS0tLS0tLVxyXG5cdCAgICA0MDRcclxuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xyXG5cclxuLmVycm9yNDA0LXdyYXBwZXIge1xyXG5cclxufSJdLCJzb3VyY2VSb290IjoiL3NvdXJjZS8ifQ== */
diff --git a/src/assets/js/scripts.js b/src/assets/js/scripts.js
index 36d04b6..9837896 100644
--- a/src/assets/js/scripts.js
+++ b/src/assets/js/scripts.js
@@ -37,4 +37,4 @@ window.helpers = (function() {
}
}
}());
-//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImhlbHBlcnMuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6InNjcmlwdHMuanMiLCJzb3VyY2VzQ29udGVudCI6WyIndXNlIHN0cmljdCc7XHJcblxyXG53aW5kb3cuaGVscGVycyA9IChmdW5jdGlvbigpIHtcclxuXHJcblx0aW5pdCgpO1xyXG5cclxuXHQvKipcclxuXHQgKiBmdW5jdGlvbiBpbml0KClcclxuXHQgKlxyXG5cdCAqIEluaXRpYWxpemUgcHVibGljIHdpbmRvdy5oZWxwZXJzIGZ1bmN0aW9uc1xyXG5cdCAqL1xyXG5cdGZ1bmN0aW9uIGluaXQoKSB7XHJcblx0XHRmaXhCcm93c2VycygpO1xyXG5cdH1cclxuXHJcblx0LyoqXHJcblx0ICogZnVuY3Rpb24gZml4QnJvd3NlcnMoKVxyXG5cdCAqXHJcblx0ICogRml4IGJyb3dzZXIgd2VpcmRuZXNzXHJcblx0ICogQ29ycmVjdCBNb2Rlcm5penIgYnVnc1xyXG5cdCAqL1xyXG5cdGZ1bmN0aW9uIGZpeEJyb3dzZXJzKCkge1xyXG5cdFx0dmFyIHVhID0gbmF2aWdhdG9yLnVzZXJBZ2VudC50b0xvd2VyQ2FzZSgpO1xyXG5cdFx0dmFyIGNocm9tZSA9IHVhLmxhc3RJbmRleE9mKCdjaHJvbWUvJykgPiAwO1xyXG5cdFx0dmFyIGNocm9tZXZlcnNpb24gPSBudWxsO1xyXG5cdFx0dmFyICRodG1sID0gJCgnaHRtbCcpO1xyXG5cdFx0XHJcblx0XHQvLyBNb2Rlcm5penIgMiBidWc6IENocm9tZSBvbiBXaW5kb3dzIDggZ2l2ZXMgYSBmYWxzZSBuZWdhdGl2ZSBmb3IgdHJhbnNmb3JtczNkIHN1cHBvcnRcclxuXHRcdC8vIEdvb2dsZSBkb2VzIG5vdCBwbGFuIHRvIGZpeCB0aGlzOyBodHRwczovL2NvZGUuZ29vZ2xlLmNvbS9wL2Nocm9taXVtL2lzc3Vlcy9kZXRhaWw/aWQ9MTI5MDA0XHJcblx0XHRpZiAoY2hyb21lKSB7XHJcblx0XHRcdGNocm9tZXZlcnNpb24gPSB1YS5zdWJzdHIodWEubGFzdEluZGV4T2YoJ2Nocm9tZS8nKSArIDcsIDIpO1xyXG5cdFx0XHRpZiAoY2hyb21ldmVyc2lvbiA+PSAxMiAmJiAkaHRtbC5oYXNDbGFzcygnbm8tY3NzdHJhbnNmb3JtczNkJykpIHtcclxuXHRcdFx0XHQkaHRtbFxyXG5cdFx0XHRcdFx0LnJlbW92ZUNsYXNzKCduby1jc3N0cmFuc2Zvcm1zM2QnKVxyXG5cdFx0XHRcdFx0LmFkZENsYXNzKCdjc3N0cmFuc2Zvcm1zM2QnKTtcclxuXHRcdFx0fVxyXG5cdFx0fVxyXG5cdH1cclxufSgpKTsiXSwic291cmNlUm9vdCI6Ii9zb3VyY2UvIn0=
\ No newline at end of file
+//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImhlbHBlcnMuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6InNjcmlwdHMuanMiLCJzb3VyY2VzQ29udGVudCI6WyIndXNlIHN0cmljdCc7XHJcblxyXG53aW5kb3cuaGVscGVycyA9IChmdW5jdGlvbigpIHtcclxuXHJcblx0aW5pdCgpO1xyXG5cclxuXHQvKipcclxuXHQgKiBmdW5jdGlvbiBpbml0KClcclxuXHQgKlxyXG5cdCAqIEluaXRpYWxpemUgcHVibGljIHdpbmRvdy5oZWxwZXJzIGZ1bmN0aW9uc1xyXG5cdCAqL1xyXG5cdGZ1bmN0aW9uIGluaXQoKSB7XHJcblx0XHRmaXhCcm93c2VycygpO1xyXG5cdH1cclxuXHJcblx0LyoqXHJcblx0ICogZnVuY3Rpb24gZml4QnJvd3NlcnMoKVxyXG5cdCAqXHJcblx0ICogRml4IGJyb3dzZXIgd2VpcmRuZXNzXHJcblx0ICogQ29ycmVjdCBNb2Rlcm5penIgYnVnc1xyXG5cdCAqL1xyXG5cdGZ1bmN0aW9uIGZpeEJyb3dzZXJzKCkge1xyXG5cdFx0dmFyIHVhID0gbmF2aWdhdG9yLnVzZXJBZ2VudC50b0xvd2VyQ2FzZSgpO1xyXG5cdFx0dmFyIGNocm9tZSA9IHVhLmxhc3RJbmRleE9mKCdjaHJvbWUvJykgPiAwO1xyXG5cdFx0dmFyIGNocm9tZXZlcnNpb24gPSBudWxsO1xyXG5cdFx0dmFyICRodG1sID0gJCgnaHRtbCcpO1xyXG5cdFx0XHJcblx0XHQvLyBNb2Rlcm5penIgMiBidWc6IENocm9tZSBvbiBXaW5kb3dzIDggZ2l2ZXMgYSBmYWxzZSBuZWdhdGl2ZSBmb3IgdHJhbnNmb3JtczNkIHN1cHBvcnRcclxuXHRcdC8vIEdvb2dsZSBkb2VzIG5vdCBwbGFuIHRvIGZpeCB0aGlzOyBodHRwczovL2NvZGUuZ29vZ2xlLmNvbS9wL2Nocm9taXVtL2lzc3Vlcy9kZXRhaWw/aWQ9MTI5MDA0XHJcblx0XHRpZiAoY2hyb21lKSB7XHJcblx0XHRcdGNocm9tZXZlcnNpb24gPSB1YS5zdWJzdHIodWEubGFzdEluZGV4T2YoJ2Nocm9tZS8nKSArIDcsIDIpO1xyXG5cdFx0XHRpZiAoY2hyb21ldmVyc2lvbiA+PSAxMiAmJiAkaHRtbC5oYXNDbGFzcygnbm8tY3NzdHJhbnNmb3JtczNkJykpIHtcclxuXHRcdFx0XHQkaHRtbFxyXG5cdFx0XHRcdFx0LnJlbW92ZUNsYXNzKCduby1jc3N0cmFuc2Zvcm1zM2QnKVxyXG5cdFx0XHRcdFx0LmFkZENsYXNzKCdjc3N0cmFuc2Zvcm1zM2QnKTtcclxuXHRcdFx0fVxyXG5cdFx0fVxyXG5cdH1cclxufSgpKTsiXSwic291cmNlUm9vdCI6Ii9zb3VyY2UvIn0=
diff --git a/src/assets/js/vendor/angular-mock.js b/src/assets/js/vendor/angular-mock.js
new file mode 100644
index 0000000..0df30cb
--- /dev/null
+++ b/src/assets/js/vendor/angular-mock.js
@@ -0,0 +1,2470 @@
+/**
+ * @license AngularJS v1.4.7
+ * (c) 2010-2015 Google, Inc. http://angularjs.org
+ * License: MIT
+ */
+(function (window, angular, undefined) {
+
+ '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 = [];
+
+ // TODO(vojta): remove this temporary api
+ self.$$completeOutstandingRequest = angular.noop;
+ self.$$incOutstandingRequestCount = angular.noop;
+
+
+ // 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) {
+ 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) {
+ if (angular.isDefined(delay)) {
+ self.defer.now += delay;
+ } else {
+ if (self.deferredFns.length) {
+ self.defer.now = self.deferredFns[self.deferredFns.length - 1].time;
+ } else {
+ throw new Error('No deferred tasks to be flushed');
+ }
+ }
+
+ while (self.deferredFns.length && self.deferredFns[0].time <= self.defer.now) {
+ self.deferredFns.shift().fn();
+ }
+ };
+
+ 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;
+ },
+
+ notifyWhenNoOutstandingRequests: function (fn) {
+ fn();
+ }
+ };
+
+
+ /**
+ * @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.
+ */
+ 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, null, (!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),
+ delay: delay,
+ 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.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) {
+ now += millis;
+ while (repeatFns.length && repeatFns[0].nextTime <= now) {
+ var task = repeatFns[0];
+ task.fn();
+ task.nextTime += task.delay;
+ repeatFns.sort(function (a, b) { return a.nextTime - b.nextTime; });
+ }
+ return millis;
+ };
+
+ return $interval;
+ }];
+ };
+
+
+ /* jshint -W101 */
+ /* The R_ISO8061_STR regex is never going to fit into the 100 char limit!
+ * This directive should go inside the anonymous function but a bug in JSHint means that it would
+ * not be enacted early enough to prevent the warning.
+ */
+ var R_ISO8061_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/;
+
+ function jsonStringToDate(string) {
+ 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 padNumber(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)) {
+ 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 padNumber(self.origDate.getUTCFullYear(), 4) + '-' +
+ padNumber(self.origDate.getUTCMonth() + 1, 2) + '-' +
+ padNumber(self.origDate.getUTCDate(), 2) + 'T' +
+ padNumber(self.origDate.getUTCHours(), 2) + ':' +
+ padNumber(self.origDate.getUTCMinutes(), 2) + ':' +
+ padNumber(self.origDate.getUTCSeconds(), 2) + '.' +
+ padNumber(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;
+ /* jshint +W101 */
+
+ angular.mock.animate = angular.module('ngAnimateMock', ['ng'])
+
+ .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('$animate', ['$delegate', '$timeout', '$browser', '$$rAF',
+ '$$forceReflow', '$$animateAsyncRun', '$rootScope',
+ function ($delegate, $timeout, $browser, $$rAF,
+ $$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,
+ flush: function () {
+ $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) {
+ 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 angular objects (scope, elements, etc..) into strings, useful for
+ * debugging.
+ *
+ * This method is also available on window, where it can be used to display objects on debug
+ * console.
+ *
+ * @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 Angular 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 expectations
Backend definitions
+ *
+ *
Syntax
+ *
.expect(...).respond(...)
+ *
.when(...).respond(...)
+ *
+ *
+ *
Typical usage
+ *
strict unit tests
+ *
loose (black-box) unit testing
+ *
+ *
+ *
Fulfills multiple requests
+ *
NO
+ *
YES
+ *
+ *
+ *
Order of requests matters
+ *
YES
+ *
NO
+ *
+ *
+ *
Request required
+ *
YES
+ *
NO
+ *
+ *
+ *
Response required
+ *
optional (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').success(function(data, status, headers) {
+ authToken = headers('A-Token');
+ $scope.user = data;
+ });
+
+ $scope.saveMessage = function(message) {
+ var headers = { 'Authorization': authToken };
+ $scope.status = 'Saving...';
+
+ $http.post('/add-msg.py', message, { headers: headers } ).success(function(response) {
+ $scope.status = '';
+ }).error(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();
+ });
+ });
+ ```
+ */
+ angular.mock.$HttpBackendProvider = function () {
+ this.$get = ['$rootScope', '$timeout', 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;
+
+ function createResponse(status, data, headers, statusText) {
+ if (angular.isFunction(status)) return status;
+
+ return function () {
+ return angular.isNumber(status)
+ ? [status, data, headers, statusText]
+ : [200, status, data, headers];
+ };
+ }
+
+ // TODO(vojta): change params to: method, url, data, headers, callback
+ function $httpBackend(method, url, data, callback, headers, timeout, withCredentials) {
+ var xhr = new MockXhr(),
+ expectation = expectations[0],
+ wasExpected = false;
+
+ function prettyPrint(data) {
+ return (angular.isString(data) || angular.isFunction(data) || data instanceof RegExp)
+ ? data
+ : angular.toJson(data);
+ }
+
+ function wrapResponse(wrapped) {
+ if (!$browser && timeout) {
+ timeout.then ? timeout.then(handleTimeout) : $timeout(handleTimeout, timeout);
+ }
+
+ return handleResponse;
+
+ function handleResponse() {
+ var response = wrapped.response(method, url, data, headers);
+ xhr.$$respHeaders = response[2];
+ callback(copy(response[0]), copy(response[1]), xhr.getAllResponseHeaders(),
+ copy(response[3] || ''));
+ }
+
+ function handleTimeout() {
+ for (var i = 0, ii = responses.length; i < ii; i++) {
+ if (responses[i] === handleResponse) {
+ responses.splice(i, 1);
+ callback(-1, undefined, '');
+ 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) {
+ $delegate(method, url, data, callback, headers, timeout, withCredentials);
+ } else throw new Error('No response defined !');
+ return;
+ }
+ }
+ throw wasExpected ?
+ new Error('No response defined !') :
+ new Error('Unexpected request: ' + method + ' ' + url + '\n' +
+ (expectation ? 'Expected ' + expectation : 'No more request expected'));
+ }
+
+ /**
+ * @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.
+ * @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)}`
+ * – 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 (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) {
+ var definition = new MockHttpExpectation(method, url, data, headers),
+ 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.
+ * @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.
+ * @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.
+ * @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.
+ * @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.
+ * @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.
+ * @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#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.
+ * @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)}`
+ * – 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 (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) {
+ var expectation = new MockHttpExpectation(method, url, data, headers),
+ 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.
+ * @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.
+ * @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.
+ * @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.
+ * @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.
+ * @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.
+ * @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.
+ * @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#flush
+ * @description
+ * Flushes all pending requests using the trained responses.
+ *
+ * @param {number=} count Number of responses to flush (in the order they arrived). If undefined,
+ * all pending requests will be flushed. If there are no pending requests when the flush method
+ * is called an exception is thrown (as this typically a sign of programming error).
+ */
+ $httpBackend.flush = function (count, digest) {
+ if (digest !== false) $rootScope.$digest();
+ if (!responses.length) throw new Error('No pending request to flush !');
+
+ if (angular.isDefined(count) && count !== null) {
+ while (count--) {
+ if (!responses.length) throw new Error('No more pending request to flush !');
+ responses.shift()();
+ }
+ } else {
+ while (responses.length) {
+ responses.shift()();
+ }
+ }
+ $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 () {
+ if (responses.length) {
+ throw new Error('Unflushed requests: ' + responses.length);
+ }
+ };
+
+
+ /**
+ * @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;
+ };
+
+ return $httpBackend;
+
+
+ function createShortMethods(prefix) {
+ angular.forEach(['GET', 'DELETE', 'JSONP', 'HEAD'], function (method) {
+ $httpBackend[prefix + method] = function (url, headers) {
+ return $httpBackend[prefix](method, url, undefined, headers);
+ };
+ });
+
+ angular.forEach(['PUT', 'POST', 'PATCH'], function (method) {
+ $httpBackend[prefix + method] = function (url, data, headers) {
+ return $httpBackend[prefix](method, url, data, headers);
+ };
+ });
+ }
+ }
+
+ function MockHttpExpectation(method, url, data, headers) {
+
+ 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;
+ };
+
+ 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));
+ }
+ return data == d;
+ };
+
+ this.toString = function () {
+ return method + ' ' + url;
+ };
+ }
+
+ 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;
+ }
+
+
+ /**
+ * @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;
+ }];
+
+ /**
+ *
+ */
+ angular.mock.$RootElementProvider = function () {
+ this.$get = function () {
+ return angular.element('');
+ };
+ };
+
+ /**
+ * @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) {
+ * $log.info(this.name);
+ * })];
+ *
+ *
+ * // In a test ...
+ *
+ * describe('myDirectiveController', function() {
+ * it('should write the bound name to the log', inject(function($controller, $log) {
+ * var ctrl = $controller('MyDirectiveController', { /* no locals */ }, { name: 'Clark Kent' });
+ * 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
+ * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global
+ * `window` object (not recommended)
+ *
+ * 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 before invoking the constructor. This is used
+ * to simulate the `bindToController` feature and simplify certain kinds of tests.
+ * @return {Object} Instance of given controller.
+ */
+ angular.mock.$ControllerDecorator = ['$delegate', function ($delegate) {
+ return function (expression, locals, later, ident) {
+ if (later && typeof later === 'object') {
+ var create = $delegate(expression, locals, true, ident);
+ angular.extend(create.instance, later);
+ return create();
+ }
+ return $delegate(expression, locals, later, ident);
+ };
+ }];
+
+
+ /**
+ * @ngdoc module
+ * @name ngMock
+ * @packageName angular-mocks
+ * @description
+ *
+ * # ngMock
+ *
+ * The `ngMock` module provides support to inject and mock Angular services into unit tests.
+ * In addition, ngMock also extends various core ng services such that they can be
+ * inspected and controlled in a synchronous manner within test code.
+ *
+ *
+ *
+ *
+ */
+ angular.module('ngMock', ['ng']).provider({
+ $browser: angular.mock.$BrowserProvider,
+ $exceptionHandler: angular.mock.$ExceptionHandlerProvider,
+ $log: angular.mock.$LogProvider,
+ $interval: angular.mock.$IntervalProvider,
+ $httpBackend: angular.mock.$HttpBackendProvider,
+ $rootElement: angular.mock.$RootElementProvider
+ }).config(['$provide', function ($provide) {
+ $provide.decorator('$timeout', angular.mock.$TimeoutDecorator);
+ $provide.decorator('$$rAF', angular.mock.$RAFDecorator);
+ $provide.decorator('$rootScope', angular.mock.$RootScopeDecorator);
+ $provide.decorator('$controller', angular.mock.$ControllerDecorator);
+ }]);
+
+ /**
+ * @ngdoc module
+ * @name ngMockE2E
+ * @module ngMockE2E
+ * @packageName angular-mocks
+ * @description
+ *
+ * The `ngMockE2E` is an angular 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);
+ }]);
+
+ /**
+ * @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
+ * myAppDev = angular.module('myAppDev', ['myApp', 'ngMockE2E']);
+ * myAppDev.run(function($httpBackend) {
+ * 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();
+ * //...
+ * });
+ * ```
+ *
+ * Afterwards, bootstrap your app with this new module.
+ */
+
+ /**
+ * @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)=} data HTTP request body.
+ * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
+ * object and returns true if the headers match the current definition.
+ * @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)}`
+ * – 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 (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.
+ * @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.
+ * @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.
+ * @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)=} data HTTP request body.
+ * @param {(Object|function(Object))=} headers HTTP headers.
+ * @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)=} data HTTP request body.
+ * @param {(Object|function(Object))=} headers HTTP headers.
+ * @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)=} data HTTP request body.
+ * @param {(Object|function(Object))=} headers HTTP headers.
+ * @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.
+ * @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
+ * @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() {
+ // jshint validthis: true
+ 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
+ * @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() {
+ // jshint validthis: true
+ 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;
+ }
+ }];
+
+
+ if (window.jasmine || window.mocha) {
+
+ var currentSpec = null,
+ annotatedFunctions = [],
+ isSpecRunning = 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);
+ };
+
+
+ (window.beforeEach || window.setup)(function () {
+ annotatedFunctions = [];
+ currentSpec = this;
+ });
+
+ (window.afterEach || window.teardown)(function () {
+ var injector = currentSpec.$injector;
+
+ annotatedFunctions.forEach(function (fn) {
+ delete fn.$inject;
+ });
+
+ angular.forEach(currentSpec.$modules, function (module) {
+ if (module && module.$$hashKey) {
+ module.$$hashKey = undefined;
+ }
+ });
+
+ currentSpec.$injector = null;
+ currentSpec.$modules = null;
+ currentSpec = null;
+
+ if (injector) {
+ injector.get('$rootElement').off();
+ }
+
+ // 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;
+ });
+
+ /**
+ * @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 they will be registered as values in the module, the key being
+ * the module name and the value being what is returned.
+ */
+ window.module = angular.mock.module = function () {
+ var moduleFns = Array.prototype.slice.call(arguments, 0);
+ return isSpecRunning() ? workFn() : workFn;
+ /////////////////////
+ function workFn() {
+ if (currentSpec.$injector) {
+ throw new Error('Injector already created, can not register a module!');
+ } else {
+ var modules = currentSpec.$modules || (currentSpec.$modules = []);
+ angular.forEach(moduleFns, function (module) {
+ if (angular.isObject(module) && !angular.isArray(module)) {
+ modules.push(function ($provide) {
+ angular.forEach(module, function (value, key) {
+ $provide.value(key, value);
+ });
+ });
+ } else {
+ modules.push(module);
+ }
+ });
+ }
+ }
+ };
+
+ /**
+ * @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 (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.toString = Error.prototype.toString;
+
+ window.inject = angular.mock.inject = function () {
+ var blockFns = Array.prototype.slice.call(arguments, 0);
+ var errorForStack = new Error('Declaration Location');
+ return isSpecRunning() ? workFn.call(currentSpec) : workFn;
+ /////////////////////
+ function workFn() {
+ var modules = currentSpec.$modules || [];
+ var strictDi = !!currentSpec.$injectorStrict;
+ 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 {
+ /* jshint -W040 *//* Jasmine explicitly provides a `this` object when calling functions */
+ injector.invoke(blockFns[i] || angular.noop, this);
+ /* jshint +W040 */
+ } 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 isSpecRunning() ? 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;
+ }
+ }
+ }
+ };
+ }
+
+
+})(window, window.angular);
\ No newline at end of file
diff --git a/src/assets/js/vendor/jasmine.js b/src/assets/js/vendor/jasmine.js
new file mode 100644
index 0000000..312d591
--- /dev/null
+++ b/src/assets/js/vendor/jasmine.js
@@ -0,0 +1,3298 @@
+/*
+Copyright (c) 2008-2015 Pivotal Labs
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+var getJasmineRequireObj = (function (jasmineGlobal) {
+ var jasmineRequire;
+
+ if (typeof module !== 'undefined' && module.exports) {
+ jasmineGlobal = global;
+ jasmineRequire = exports;
+ } else {
+ if (typeof window !== 'undefined' && typeof window.toString === 'function' && window.toString() === '[object GjsGlobal]') {
+ jasmineGlobal = window;
+ }
+ jasmineRequire = jasmineGlobal.jasmineRequire = jasmineGlobal.jasmineRequire || {};
+ }
+
+ function getJasmineRequire() {
+ return jasmineRequire;
+ }
+
+ getJasmineRequire().core = function(jRequire) {
+ var j$ = {};
+
+ jRequire.base(j$, jasmineGlobal);
+ j$.util = jRequire.util();
+ j$.errors = jRequire.errors();
+ j$.Any = jRequire.Any(j$);
+ j$.Anything = jRequire.Anything(j$);
+ j$.CallTracker = jRequire.CallTracker();
+ j$.MockDate = jRequire.MockDate();
+ j$.Clock = jRequire.Clock();
+ j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler();
+ j$.Env = jRequire.Env(j$);
+ j$.ExceptionFormatter = jRequire.ExceptionFormatter();
+ j$.Expectation = jRequire.Expectation();
+ j$.buildExpectationResult = jRequire.buildExpectationResult();
+ j$.JsApiReporter = jRequire.JsApiReporter();
+ j$.matchersUtil = jRequire.matchersUtil(j$);
+ j$.ObjectContaining = jRequire.ObjectContaining(j$);
+ j$.ArrayContaining = jRequire.ArrayContaining(j$);
+ j$.pp = jRequire.pp(j$);
+ j$.QueueRunner = jRequire.QueueRunner(j$);
+ j$.ReportDispatcher = jRequire.ReportDispatcher();
+ j$.Spec = jRequire.Spec(j$);
+ j$.SpyRegistry = jRequire.SpyRegistry(j$);
+ j$.SpyStrategy = jRequire.SpyStrategy();
+ j$.StringMatching = jRequire.StringMatching(j$);
+ j$.Suite = jRequire.Suite(j$);
+ j$.Timer = jRequire.Timer();
+ j$.TreeProcessor = jRequire.TreeProcessor();
+ j$.version = jRequire.version();
+
+ j$.matchers = jRequire.requireMatchers(jRequire, j$);
+
+ return j$;
+ };
+
+ return getJasmineRequire;
+})(this);
+
+getJasmineRequireObj().requireMatchers = function(jRequire, j$) {
+ var availableMatchers = [
+ 'toBe',
+ 'toBeCloseTo',
+ 'toBeDefined',
+ 'toBeFalsy',
+ 'toBeGreaterThan',
+ 'toBeLessThan',
+ 'toBeNaN',
+ 'toBeNull',
+ 'toBeTruthy',
+ 'toBeUndefined',
+ 'toContain',
+ 'toEqual',
+ 'toHaveBeenCalled',
+ 'toHaveBeenCalledWith',
+ 'toMatch',
+ 'toThrow',
+ 'toThrowError'
+ ],
+ matchers = {};
+
+ for (var i = 0; i < availableMatchers.length; i++) {
+ var name = availableMatchers[i];
+ matchers[name] = jRequire[name](j$);
+ }
+
+ return matchers;
+};
+
+getJasmineRequireObj().base = function(j$, jasmineGlobal) {
+ j$.unimplementedMethod_ = function() {
+ throw new Error('unimplemented method');
+ };
+
+ j$.MAX_PRETTY_PRINT_DEPTH = 40;
+ j$.MAX_PRETTY_PRINT_ARRAY_LENGTH = 100;
+ j$.DEFAULT_TIMEOUT_INTERVAL = 5000;
+
+ j$.getGlobal = function() {
+ return jasmineGlobal;
+ };
+
+ j$.getEnv = function(options) {
+ var env = j$.currentEnv_ = j$.currentEnv_ || new j$.Env(options);
+ //jasmine. singletons in here (setTimeout blah blah).
+ return env;
+ };
+
+ j$.isArray_ = function(value) {
+ return j$.isA_('Array', value);
+ };
+
+ j$.isString_ = function(value) {
+ return j$.isA_('String', value);
+ };
+
+ j$.isNumber_ = function(value) {
+ return j$.isA_('Number', value);
+ };
+
+ j$.isA_ = function(typeName, value) {
+ return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
+ };
+
+ j$.isDomNode = function(obj) {
+ return obj.nodeType > 0;
+ };
+
+ j$.fnNameFor = function(func) {
+ return func.name || func.toString().match(/^\s*function\s*(\w*)\s*\(/)[1];
+ };
+
+ j$.any = function(clazz) {
+ return new j$.Any(clazz);
+ };
+
+ j$.anything = function() {
+ return new j$.Anything();
+ };
+
+ j$.objectContaining = function(sample) {
+ return new j$.ObjectContaining(sample);
+ };
+
+ j$.stringMatching = function(expected) {
+ return new j$.StringMatching(expected);
+ };
+
+ j$.arrayContaining = function(sample) {
+ return new j$.ArrayContaining(sample);
+ };
+
+ j$.createSpy = function(name, originalFn) {
+
+ var spyStrategy = new j$.SpyStrategy({
+ name: name,
+ fn: originalFn,
+ getSpy: function() { return spy; }
+ }),
+ callTracker = new j$.CallTracker(),
+ spy = function() {
+ var callData = {
+ object: this,
+ args: Array.prototype.slice.apply(arguments)
+ };
+
+ callTracker.track(callData);
+ var returnValue = spyStrategy.exec.apply(this, arguments);
+ callData.returnValue = returnValue;
+
+ return returnValue;
+ };
+
+ for (var prop in originalFn) {
+ if (prop === 'and' || prop === 'calls') {
+ throw new Error('Jasmine spies would overwrite the \'and\' and \'calls\' properties on the object being spied upon');
+ }
+
+ spy[prop] = originalFn[prop];
+ }
+
+ spy.and = spyStrategy;
+ spy.calls = callTracker;
+
+ return spy;
+ };
+
+ j$.isSpy = function(putativeSpy) {
+ if (!putativeSpy) {
+ return false;
+ }
+ return putativeSpy.and instanceof j$.SpyStrategy &&
+ putativeSpy.calls instanceof j$.CallTracker;
+ };
+
+ j$.createSpyObj = function(baseName, methodNames) {
+ if (j$.isArray_(baseName) && j$.util.isUndefined(methodNames)) {
+ methodNames = baseName;
+ baseName = 'unknown';
+ }
+
+ if (!j$.isArray_(methodNames) || methodNames.length === 0) {
+ throw 'createSpyObj requires a non-empty array of method names to create spies for';
+ }
+ var obj = {};
+ for (var i = 0; i < methodNames.length; i++) {
+ obj[methodNames[i]] = j$.createSpy(baseName + '.' + methodNames[i]);
+ }
+ return obj;
+ };
+};
+
+getJasmineRequireObj().util = function() {
+
+ var util = {};
+
+ util.inherit = function(childClass, parentClass) {
+ var Subclass = function() {
+ };
+ Subclass.prototype = parentClass.prototype;
+ childClass.prototype = new Subclass();
+ };
+
+ util.htmlEscape = function(str) {
+ if (!str) {
+ return str;
+ }
+ return str.replace(/&/g, '&')
+ .replace(//g, '>');
+ };
+
+ util.argsToArray = function(args) {
+ var arrayOfArgs = [];
+ for (var i = 0; i < args.length; i++) {
+ arrayOfArgs.push(args[i]);
+ }
+ return arrayOfArgs;
+ };
+
+ util.isUndefined = function(obj) {
+ return obj === void 0;
+ };
+
+ util.arrayContains = function(array, search) {
+ var i = array.length;
+ while (i--) {
+ if (array[i] === search) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ util.clone = function(obj) {
+ if (Object.prototype.toString.apply(obj) === '[object Array]') {
+ return obj.slice();
+ }
+
+ var cloned = {};
+ for (var prop in obj) {
+ if (obj.hasOwnProperty(prop)) {
+ cloned[prop] = obj[prop];
+ }
+ }
+
+ return cloned;
+ };
+
+ return util;
+};
+
+getJasmineRequireObj().Spec = function(j$) {
+ function Spec(attrs) {
+ this.expectationFactory = attrs.expectationFactory;
+ this.resultCallback = attrs.resultCallback || function() {};
+ this.id = attrs.id;
+ this.description = attrs.description || '';
+ this.queueableFn = attrs.queueableFn;
+ this.beforeAndAfterFns = attrs.beforeAndAfterFns || function() { return {befores: [], afters: []}; };
+ this.userContext = attrs.userContext || function() { return {}; };
+ this.onStart = attrs.onStart || function() {};
+ this.getSpecName = attrs.getSpecName || function() { return ''; };
+ this.expectationResultFactory = attrs.expectationResultFactory || function() { };
+ this.queueRunnerFactory = attrs.queueRunnerFactory || function() {};
+ this.catchingExceptions = attrs.catchingExceptions || function() { return true; };
+ this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure;
+
+ if (!this.queueableFn.fn) {
+ this.pend();
+ }
+
+ this.result = {
+ id: this.id,
+ description: this.description,
+ fullName: this.getFullName(),
+ failedExpectations: [],
+ passedExpectations: [],
+ pendingReason: ''
+ };
+ }
+
+ Spec.prototype.addExpectationResult = function(passed, data, isError) {
+ var expectationResult = this.expectationResultFactory(data);
+ if (passed) {
+ this.result.passedExpectations.push(expectationResult);
+ } else {
+ this.result.failedExpectations.push(expectationResult);
+
+ if (this.throwOnExpectationFailure && !isError) {
+ throw new j$.errors.ExpectationFailed();
+ }
+ }
+ };
+
+ Spec.prototype.expect = function(actual) {
+ return this.expectationFactory(actual, this);
+ };
+
+ Spec.prototype.execute = function(onComplete, enabled) {
+ var self = this;
+
+ this.onStart(this);
+
+ if (!this.isExecutable() || this.markedPending || enabled === false) {
+ complete(enabled);
+ return;
+ }
+
+ var fns = this.beforeAndAfterFns();
+ var allFns = fns.befores.concat(this.queueableFn).concat(fns.afters);
+
+ this.queueRunnerFactory({
+ queueableFns: allFns,
+ onException: function() { self.onException.apply(self, arguments); },
+ onComplete: complete,
+ userContext: this.userContext()
+ });
+
+ function complete(enabledAgain) {
+ self.result.status = self.status(enabledAgain);
+ self.resultCallback(self.result);
+
+ if (onComplete) {
+ onComplete();
+ }
+ }
+ };
+
+ Spec.prototype.onException = function onException(e) {
+ if (Spec.isPendingSpecException(e)) {
+ this.pend(extractCustomPendingMessage(e));
+ return;
+ }
+
+ if (e instanceof j$.errors.ExpectationFailed) {
+ return;
+ }
+
+ this.addExpectationResult(false, {
+ matcherName: '',
+ passed: false,
+ expected: '',
+ actual: '',
+ error: e
+ }, true);
+ };
+
+ Spec.prototype.disable = function() {
+ this.disabled = true;
+ };
+
+ Spec.prototype.pend = function(message) {
+ this.markedPending = true;
+ if (message) {
+ this.result.pendingReason = message;
+ }
+ };
+
+ Spec.prototype.getResult = function() {
+ this.result.status = this.status();
+ return this.result;
+ };
+
+ Spec.prototype.status = function(enabled) {
+ if (this.disabled || enabled === false) {
+ return 'disabled';
+ }
+
+ if (this.markedPending) {
+ return 'pending';
+ }
+
+ if (this.result.failedExpectations.length > 0) {
+ return 'failed';
+ } else {
+ return 'passed';
+ }
+ };
+
+ Spec.prototype.isExecutable = function() {
+ return !this.disabled;
+ };
+
+ Spec.prototype.getFullName = function() {
+ return this.getSpecName(this);
+ };
+
+ var extractCustomPendingMessage = function(e) {
+ var fullMessage = e.toString(),
+ boilerplateStart = fullMessage.indexOf(Spec.pendingSpecExceptionMessage),
+ boilerplateEnd = boilerplateStart + Spec.pendingSpecExceptionMessage.length;
+
+ return fullMessage.substr(boilerplateEnd);
+ };
+
+ Spec.pendingSpecExceptionMessage = '=> marked Pending';
+
+ Spec.isPendingSpecException = function(e) {
+ return !!(e && e.toString && e.toString().indexOf(Spec.pendingSpecExceptionMessage) !== -1);
+ };
+
+ return Spec;
+};
+
+if (typeof window == void 0 && typeof exports == 'object') {
+ exports.Spec = jasmineRequire.Spec;
+}
+
+getJasmineRequireObj().Env = function(j$) {
+ function Env(options) {
+ options = options || {};
+
+ var self = this;
+ var global = options.global || j$.getGlobal();
+
+ var totalSpecsDefined = 0;
+
+ var catchExceptions = true;
+
+ var realSetTimeout = j$.getGlobal().setTimeout;
+ var realClearTimeout = j$.getGlobal().clearTimeout;
+ this.clock = new j$.Clock(global, function () { return new j$.DelayedFunctionScheduler(); }, new j$.MockDate(global));
+
+ var runnableLookupTable = {};
+ var runnableResources = {};
+
+ var currentSpec = null;
+ var currentlyExecutingSuites = [];
+ var currentDeclarationSuite = null;
+ var throwOnExpectationFailure = false;
+
+ var currentSuite = function() {
+ return currentlyExecutingSuites[currentlyExecutingSuites.length - 1];
+ };
+
+ var currentRunnable = function() {
+ return currentSpec || currentSuite();
+ };
+
+ var reporter = new j$.ReportDispatcher([
+ 'jasmineStarted',
+ 'jasmineDone',
+ 'suiteStarted',
+ 'suiteDone',
+ 'specStarted',
+ 'specDone'
+ ]);
+
+ this.specFilter = function() {
+ return true;
+ };
+
+ this.addCustomEqualityTester = function(tester) {
+ if(!currentRunnable()) {
+ throw new Error('Custom Equalities must be added in a before function or a spec');
+ }
+ runnableResources[currentRunnable().id].customEqualityTesters.push(tester);
+ };
+
+ this.addMatchers = function(matchersToAdd) {
+ if(!currentRunnable()) {
+ throw new Error('Matchers must be added in a before function or a spec');
+ }
+ var customMatchers = runnableResources[currentRunnable().id].customMatchers;
+ for (var matcherName in matchersToAdd) {
+ customMatchers[matcherName] = matchersToAdd[matcherName];
+ }
+ };
+
+ j$.Expectation.addCoreMatchers(j$.matchers);
+
+ var nextSpecId = 0;
+ var getNextSpecId = function() {
+ return 'spec' + nextSpecId++;
+ };
+
+ var nextSuiteId = 0;
+ var getNextSuiteId = function() {
+ return 'suite' + nextSuiteId++;
+ };
+
+ var expectationFactory = function(actual, spec) {
+ return j$.Expectation.Factory({
+ util: j$.matchersUtil,
+ customEqualityTesters: runnableResources[spec.id].customEqualityTesters,
+ customMatchers: runnableResources[spec.id].customMatchers,
+ actual: actual,
+ addExpectationResult: addExpectationResult
+ });
+
+ function addExpectationResult(passed, result) {
+ return spec.addExpectationResult(passed, result);
+ }
+ };
+
+ var defaultResourcesForRunnable = function(id, parentRunnableId) {
+ var resources = {spies: [], customEqualityTesters: [], customMatchers: {}};
+
+ if(runnableResources[parentRunnableId]){
+ resources.customEqualityTesters = j$.util.clone(runnableResources[parentRunnableId].customEqualityTesters);
+ resources.customMatchers = j$.util.clone(runnableResources[parentRunnableId].customMatchers);
+ }
+
+ runnableResources[id] = resources;
+ };
+
+ var clearResourcesForRunnable = function(id) {
+ spyRegistry.clearSpies();
+ delete runnableResources[id];
+ };
+
+ var beforeAndAfterFns = function(suite) {
+ return function() {
+ var befores = [],
+ afters = [];
+
+ while(suite) {
+ befores = befores.concat(suite.beforeFns);
+ afters = afters.concat(suite.afterFns);
+
+ suite = suite.parentSuite;
+ }
+
+ return {
+ befores: befores.reverse(),
+ afters: afters
+ };
+ };
+ };
+
+ var getSpecName = function(spec, suite) {
+ return suite.getFullName() + ' ' + spec.description;
+ };
+
+ // TODO: we may just be able to pass in the fn instead of wrapping here
+ var buildExpectationResult = j$.buildExpectationResult,
+ exceptionFormatter = new j$.ExceptionFormatter(),
+ expectationResultFactory = function(attrs) {
+ attrs.messageFormatter = exceptionFormatter.message;
+ attrs.stackFormatter = exceptionFormatter.stack;
+
+ return buildExpectationResult(attrs);
+ };
+
+ // TODO: fix this naming, and here's where the value comes in
+ this.catchExceptions = function(value) {
+ catchExceptions = !!value;
+ return catchExceptions;
+ };
+
+ this.catchingExceptions = function() {
+ return catchExceptions;
+ };
+
+ var maximumSpecCallbackDepth = 20;
+ var currentSpecCallbackDepth = 0;
+
+ function clearStack(fn) {
+ currentSpecCallbackDepth++;
+ if (currentSpecCallbackDepth >= maximumSpecCallbackDepth) {
+ currentSpecCallbackDepth = 0;
+ realSetTimeout(fn, 0);
+ } else {
+ fn();
+ }
+ }
+
+ var catchException = function(e) {
+ return j$.Spec.isPendingSpecException(e) || catchExceptions;
+ };
+
+ this.throwOnExpectationFailure = function(value) {
+ throwOnExpectationFailure = !!value;
+ };
+
+ this.throwingExpectationFailures = function() {
+ return throwOnExpectationFailure;
+ };
+
+ var queueRunnerFactory = function(options) {
+ options.catchException = catchException;
+ options.clearStack = options.clearStack || clearStack;
+ options.timeout = {setTimeout: realSetTimeout, clearTimeout: realClearTimeout};
+ options.fail = self.fail;
+
+ new j$.QueueRunner(options).execute();
+ };
+
+ var topSuite = new j$.Suite({
+ env: this,
+ id: getNextSuiteId(),
+ description: 'Jasmine__TopLevel__Suite',
+ queueRunner: queueRunnerFactory
+ });
+ runnableLookupTable[topSuite.id] = topSuite;
+ defaultResourcesForRunnable(topSuite.id);
+ currentDeclarationSuite = topSuite;
+
+ this.topSuite = function() {
+ return topSuite;
+ };
+
+ this.execute = function(runnablesToRun) {
+ if(!runnablesToRun) {
+ if (focusedRunnables.length) {
+ runnablesToRun = focusedRunnables;
+ } else {
+ runnablesToRun = [topSuite.id];
+ }
+ }
+ var processor = new j$.TreeProcessor({
+ tree: topSuite,
+ runnableIds: runnablesToRun,
+ queueRunnerFactory: queueRunnerFactory,
+ nodeStart: function(suite) {
+ currentlyExecutingSuites.push(suite);
+ defaultResourcesForRunnable(suite.id, suite.parentSuite.id);
+ reporter.suiteStarted(suite.result);
+ },
+ nodeComplete: function(suite, result) {
+ if (!suite.disabled) {
+ clearResourcesForRunnable(suite.id);
+ }
+ currentlyExecutingSuites.pop();
+ reporter.suiteDone(result);
+ }
+ });
+
+ if(!processor.processTree().valid) {
+ throw new Error('Invalid order: would cause a beforeAll or afterAll to be run multiple times');
+ }
+
+ reporter.jasmineStarted({
+ totalSpecsDefined: totalSpecsDefined
+ });
+
+ processor.execute(reporter.jasmineDone);
+ };
+
+ this.addReporter = function(reporterToAdd) {
+ reporter.addReporter(reporterToAdd);
+ };
+
+ var spyRegistry = new j$.SpyRegistry({currentSpies: function() {
+ if(!currentRunnable()) {
+ throw new Error('Spies must be created in a before function or a spec');
+ }
+ return runnableResources[currentRunnable().id].spies;
+ }});
+
+ this.spyOn = function() {
+ return spyRegistry.spyOn.apply(spyRegistry, arguments);
+ };
+
+ var suiteFactory = function(description) {
+ var suite = new j$.Suite({
+ env: self,
+ id: getNextSuiteId(),
+ description: description,
+ parentSuite: currentDeclarationSuite,
+ expectationFactory: expectationFactory,
+ expectationResultFactory: expectationResultFactory,
+ throwOnExpectationFailure: throwOnExpectationFailure
+ });
+
+ runnableLookupTable[suite.id] = suite;
+ return suite;
+ };
+
+ this.describe = function(description, specDefinitions) {
+ var suite = suiteFactory(description);
+ addSpecsToSuite(suite, specDefinitions);
+ return suite;
+ };
+
+ this.xdescribe = function(description, specDefinitions) {
+ var suite = this.describe(description, specDefinitions);
+ suite.disable();
+ return suite;
+ };
+
+ var focusedRunnables = [];
+
+ this.fdescribe = function(description, specDefinitions) {
+ var suite = suiteFactory(description);
+ suite.isFocused = true;
+
+ focusedRunnables.push(suite.id);
+ unfocusAncestor();
+ addSpecsToSuite(suite, specDefinitions);
+
+ return suite;
+ };
+
+ function addSpecsToSuite(suite, specDefinitions) {
+ var parentSuite = currentDeclarationSuite;
+ parentSuite.addChild(suite);
+ currentDeclarationSuite = suite;
+
+ var declarationError = null;
+ try {
+ specDefinitions.call(suite);
+ } catch (e) {
+ declarationError = e;
+ }
+
+ if (declarationError) {
+ self.it('encountered a declaration exception', function() {
+ throw declarationError;
+ });
+ }
+
+ currentDeclarationSuite = parentSuite;
+ }
+
+ function findFocusedAncestor(suite) {
+ while (suite) {
+ if (suite.isFocused) {
+ return suite.id;
+ }
+ suite = suite.parentSuite;
+ }
+
+ return null;
+ }
+
+ function unfocusAncestor() {
+ var focusedAncestor = findFocusedAncestor(currentDeclarationSuite);
+ if (focusedAncestor) {
+ for (var i = 0; i < focusedRunnables.length; i++) {
+ if (focusedRunnables[i] === focusedAncestor) {
+ focusedRunnables.splice(i, 1);
+ break;
+ }
+ }
+ }
+ }
+
+ var specFactory = function(description, fn, suite, timeout) {
+ totalSpecsDefined++;
+ var spec = new j$.Spec({
+ id: getNextSpecId(),
+ beforeAndAfterFns: beforeAndAfterFns(suite),
+ expectationFactory: expectationFactory,
+ resultCallback: specResultCallback,
+ getSpecName: function(spec) {
+ return getSpecName(spec, suite);
+ },
+ onStart: specStarted,
+ description: description,
+ expectationResultFactory: expectationResultFactory,
+ queueRunnerFactory: queueRunnerFactory,
+ userContext: function() { return suite.clonedSharedUserContext(); },
+ queueableFn: {
+ fn: fn,
+ timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
+ },
+ throwOnExpectationFailure: throwOnExpectationFailure
+ });
+
+ runnableLookupTable[spec.id] = spec;
+
+ if (!self.specFilter(spec)) {
+ spec.disable();
+ }
+
+ return spec;
+
+ function specResultCallback(result) {
+ clearResourcesForRunnable(spec.id);
+ currentSpec = null;
+ reporter.specDone(result);
+ }
+
+ function specStarted(spec) {
+ currentSpec = spec;
+ defaultResourcesForRunnable(spec.id, suite.id);
+ reporter.specStarted(spec.result);
+ }
+ };
+
+ this.it = function(description, fn, timeout) {
+ var spec = specFactory(description, fn, currentDeclarationSuite, timeout);
+ currentDeclarationSuite.addChild(spec);
+ return spec;
+ };
+
+ this.xit = function() {
+ var spec = this.it.apply(this, arguments);
+ spec.pend();
+ return spec;
+ };
+
+ this.fit = function(){
+ var spec = this.it.apply(this, arguments);
+
+ focusedRunnables.push(spec.id);
+ unfocusAncestor();
+ return spec;
+ };
+
+ this.expect = function(actual) {
+ if (!currentRunnable()) {
+ throw new Error('\'expect\' was used when there was no current spec, this could be because an asynchronous test timed out');
+ }
+
+ return currentRunnable().expect(actual);
+ };
+
+ this.beforeEach = function(beforeEachFunction, timeout) {
+ currentDeclarationSuite.beforeEach({
+ fn: beforeEachFunction,
+ timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
+ });
+ };
+
+ this.beforeAll = function(beforeAllFunction, timeout) {
+ currentDeclarationSuite.beforeAll({
+ fn: beforeAllFunction,
+ timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
+ });
+ };
+
+ this.afterEach = function(afterEachFunction, timeout) {
+ currentDeclarationSuite.afterEach({
+ fn: afterEachFunction,
+ timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
+ });
+ };
+
+ this.afterAll = function(afterAllFunction, timeout) {
+ currentDeclarationSuite.afterAll({
+ fn: afterAllFunction,
+ timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
+ });
+ };
+
+ this.pending = function(message) {
+ var fullMessage = j$.Spec.pendingSpecExceptionMessage;
+ if(message) {
+ fullMessage += message;
+ }
+ throw fullMessage;
+ };
+
+ this.fail = function(error) {
+ var message = 'Failed';
+ if (error) {
+ message += ': ';
+ message += error.message || error;
+ }
+
+ currentRunnable().addExpectationResult(false, {
+ matcherName: '',
+ passed: false,
+ expected: '',
+ actual: '',
+ message: message,
+ error: error && error.message ? error : null
+ });
+ };
+ }
+
+ return Env;
+};
+
+getJasmineRequireObj().JsApiReporter = function() {
+
+ var noopTimer = {
+ start: function(){},
+ elapsed: function(){ return 0; }
+ };
+
+ function JsApiReporter(options) {
+ var timer = options.timer || noopTimer,
+ status = 'loaded';
+
+ this.started = false;
+ this.finished = false;
+
+ this.jasmineStarted = function() {
+ this.started = true;
+ status = 'started';
+ timer.start();
+ };
+
+ var executionTime;
+
+ this.jasmineDone = function() {
+ this.finished = true;
+ executionTime = timer.elapsed();
+ status = 'done';
+ };
+
+ this.status = function() {
+ return status;
+ };
+
+ var suites = [],
+ suites_hash = {};
+
+ this.suiteStarted = function(result) {
+ suites_hash[result.id] = result;
+ };
+
+ this.suiteDone = function(result) {
+ storeSuite(result);
+ };
+
+ this.suiteResults = function(index, length) {
+ return suites.slice(index, index + length);
+ };
+
+ function storeSuite(result) {
+ suites.push(result);
+ suites_hash[result.id] = result;
+ }
+
+ this.suites = function() {
+ return suites_hash;
+ };
+
+ var specs = [];
+
+ this.specDone = function(result) {
+ specs.push(result);
+ };
+
+ this.specResults = function(index, length) {
+ return specs.slice(index, index + length);
+ };
+
+ this.specs = function() {
+ return specs;
+ };
+
+ this.executionTime = function() {
+ return executionTime;
+ };
+
+ }
+
+ return JsApiReporter;
+};
+
+getJasmineRequireObj().CallTracker = function() {
+
+ function CallTracker() {
+ var calls = [];
+
+ this.track = function(context) {
+ calls.push(context);
+ };
+
+ this.any = function() {
+ return !!calls.length;
+ };
+
+ this.count = function() {
+ return calls.length;
+ };
+
+ this.argsFor = function(index) {
+ var call = calls[index];
+ return call ? call.args : [];
+ };
+
+ this.all = function() {
+ return calls;
+ };
+
+ this.allArgs = function() {
+ var callArgs = [];
+ for(var i = 0; i < calls.length; i++){
+ callArgs.push(calls[i].args);
+ }
+
+ return callArgs;
+ };
+
+ this.first = function() {
+ return calls[0];
+ };
+
+ this.mostRecent = function() {
+ return calls[calls.length - 1];
+ };
+
+ this.reset = function() {
+ calls = [];
+ };
+ }
+
+ return CallTracker;
+};
+
+getJasmineRequireObj().Clock = function() {
+ function Clock(global, delayedFunctionSchedulerFactory, mockDate) {
+ var self = this,
+ realTimingFunctions = {
+ setTimeout: global.setTimeout,
+ clearTimeout: global.clearTimeout,
+ setInterval: global.setInterval,
+ clearInterval: global.clearInterval
+ },
+ fakeTimingFunctions = {
+ setTimeout: setTimeout,
+ clearTimeout: clearTimeout,
+ setInterval: setInterval,
+ clearInterval: clearInterval
+ },
+ installed = false,
+ delayedFunctionScheduler,
+ timer;
+
+
+ self.install = function() {
+ if(!originalTimingFunctionsIntact()) {
+ throw new Error('Jasmine Clock was unable to install over custom global timer functions. Is the clock already installed?');
+ }
+ replace(global, fakeTimingFunctions);
+ timer = fakeTimingFunctions;
+ delayedFunctionScheduler = delayedFunctionSchedulerFactory();
+ installed = true;
+
+ return self;
+ };
+
+ self.uninstall = function() {
+ delayedFunctionScheduler = null;
+ mockDate.uninstall();
+ replace(global, realTimingFunctions);
+
+ timer = realTimingFunctions;
+ installed = false;
+ };
+
+ self.withMock = function(closure) {
+ this.install();
+ try {
+ closure();
+ } finally {
+ this.uninstall();
+ }
+ };
+
+ self.mockDate = function(initialDate) {
+ mockDate.install(initialDate);
+ };
+
+ self.setTimeout = function(fn, delay, params) {
+ if (legacyIE()) {
+ if (arguments.length > 2) {
+ throw new Error('IE < 9 cannot support extra params to setTimeout without a polyfill');
+ }
+ return timer.setTimeout(fn, delay);
+ }
+ return Function.prototype.apply.apply(timer.setTimeout, [global, arguments]);
+ };
+
+ self.setInterval = function(fn, delay, params) {
+ if (legacyIE()) {
+ if (arguments.length > 2) {
+ throw new Error('IE < 9 cannot support extra params to setInterval without a polyfill');
+ }
+ return timer.setInterval(fn, delay);
+ }
+ return Function.prototype.apply.apply(timer.setInterval, [global, arguments]);
+ };
+
+ self.clearTimeout = function(id) {
+ return Function.prototype.call.apply(timer.clearTimeout, [global, id]);
+ };
+
+ self.clearInterval = function(id) {
+ return Function.prototype.call.apply(timer.clearInterval, [global, id]);
+ };
+
+ self.tick = function(millis) {
+ if (installed) {
+ mockDate.tick(millis);
+ delayedFunctionScheduler.tick(millis);
+ } else {
+ throw new Error('Mock clock is not installed, use jasmine.clock().install()');
+ }
+ };
+
+ return self;
+
+ function originalTimingFunctionsIntact() {
+ return global.setTimeout === realTimingFunctions.setTimeout &&
+ global.clearTimeout === realTimingFunctions.clearTimeout &&
+ global.setInterval === realTimingFunctions.setInterval &&
+ global.clearInterval === realTimingFunctions.clearInterval;
+ }
+
+ function legacyIE() {
+ //if these methods are polyfilled, apply will be present
+ return !(realTimingFunctions.setTimeout || realTimingFunctions.setInterval).apply;
+ }
+
+ function replace(dest, source) {
+ for (var prop in source) {
+ dest[prop] = source[prop];
+ }
+ }
+
+ function setTimeout(fn, delay) {
+ return delayedFunctionScheduler.scheduleFunction(fn, delay, argSlice(arguments, 2));
+ }
+
+ function clearTimeout(id) {
+ return delayedFunctionScheduler.removeFunctionWithId(id);
+ }
+
+ function setInterval(fn, interval) {
+ return delayedFunctionScheduler.scheduleFunction(fn, interval, argSlice(arguments, 2), true);
+ }
+
+ function clearInterval(id) {
+ return delayedFunctionScheduler.removeFunctionWithId(id);
+ }
+
+ function argSlice(argsObj, n) {
+ return Array.prototype.slice.call(argsObj, n);
+ }
+ }
+
+ return Clock;
+};
+
+getJasmineRequireObj().DelayedFunctionScheduler = function() {
+ function DelayedFunctionScheduler() {
+ var self = this;
+ var scheduledLookup = [];
+ var scheduledFunctions = {};
+ var currentTime = 0;
+ var delayedFnCount = 0;
+
+ self.tick = function(millis) {
+ millis = millis || 0;
+ var endTime = currentTime + millis;
+
+ runScheduledFunctions(endTime);
+ currentTime = endTime;
+ };
+
+ self.scheduleFunction = function(funcToCall, millis, params, recurring, timeoutKey, runAtMillis) {
+ var f;
+ if (typeof(funcToCall) === 'string') {
+ /* jshint evil: true */
+ f = function() { return eval(funcToCall); };
+ /* jshint evil: false */
+ } else {
+ f = funcToCall;
+ }
+
+ millis = millis || 0;
+ timeoutKey = timeoutKey || ++delayedFnCount;
+ runAtMillis = runAtMillis || (currentTime + millis);
+
+ var funcToSchedule = {
+ runAtMillis: runAtMillis,
+ funcToCall: f,
+ recurring: recurring,
+ params: params,
+ timeoutKey: timeoutKey,
+ millis: millis
+ };
+
+ if (runAtMillis in scheduledFunctions) {
+ scheduledFunctions[runAtMillis].push(funcToSchedule);
+ } else {
+ scheduledFunctions[runAtMillis] = [funcToSchedule];
+ scheduledLookup.push(runAtMillis);
+ scheduledLookup.sort(function (a, b) {
+ return a - b;
+ });
+ }
+
+ return timeoutKey;
+ };
+
+ self.removeFunctionWithId = function(timeoutKey) {
+ for (var runAtMillis in scheduledFunctions) {
+ var funcs = scheduledFunctions[runAtMillis];
+ var i = indexOfFirstToPass(funcs, function (func) {
+ return func.timeoutKey === timeoutKey;
+ });
+
+ if (i > -1) {
+ if (funcs.length === 1) {
+ delete scheduledFunctions[runAtMillis];
+ deleteFromLookup(runAtMillis);
+ } else {
+ funcs.splice(i, 1);
+ }
+
+ // intervals get rescheduled when executed, so there's never more
+ // than a single scheduled function with a given timeoutKey
+ break;
+ }
+ }
+ };
+
+ return self;
+
+ function indexOfFirstToPass(array, testFn) {
+ var index = -1;
+
+ for (var i = 0; i < array.length; ++i) {
+ if (testFn(array[i])) {
+ index = i;
+ break;
+ }
+ }
+
+ return index;
+ }
+
+ function deleteFromLookup(key) {
+ var value = Number(key);
+ var i = indexOfFirstToPass(scheduledLookup, function (millis) {
+ return millis === value;
+ });
+
+ if (i > -1) {
+ scheduledLookup.splice(i, 1);
+ }
+ }
+
+ function reschedule(scheduledFn) {
+ self.scheduleFunction(scheduledFn.funcToCall,
+ scheduledFn.millis,
+ scheduledFn.params,
+ true,
+ scheduledFn.timeoutKey,
+ scheduledFn.runAtMillis + scheduledFn.millis);
+ }
+
+ function forEachFunction(funcsToRun, callback) {
+ for (var i = 0; i < funcsToRun.length; ++i) {
+ callback(funcsToRun[i]);
+ }
+ }
+
+ function runScheduledFunctions(endTime) {
+ if (scheduledLookup.length === 0 || scheduledLookup[0] > endTime) {
+ return;
+ }
+
+ do {
+ currentTime = scheduledLookup.shift();
+
+ var funcsToRun = scheduledFunctions[currentTime];
+ delete scheduledFunctions[currentTime];
+
+ forEachFunction(funcsToRun, function(funcToRun) {
+ if (funcToRun.recurring) {
+ reschedule(funcToRun);
+ }
+ });
+
+ forEachFunction(funcsToRun, function(funcToRun) {
+ funcToRun.funcToCall.apply(null, funcToRun.params || []);
+ });
+ } while (scheduledLookup.length > 0 &&
+ // checking first if we're out of time prevents setTimeout(0)
+ // scheduled in a funcToRun from forcing an extra iteration
+ currentTime !== endTime &&
+ scheduledLookup[0] <= endTime);
+ }
+ }
+
+ return DelayedFunctionScheduler;
+};
+
+getJasmineRequireObj().ExceptionFormatter = function() {
+ function ExceptionFormatter() {
+ this.message = function(error) {
+ var message = '';
+
+ if (error.name && error.message) {
+ message += error.name + ': ' + error.message;
+ } else {
+ message += error.toString() + ' thrown';
+ }
+
+ if (error.fileName || error.sourceURL) {
+ message += ' in ' + (error.fileName || error.sourceURL);
+ }
+
+ if (error.line || error.lineNumber) {
+ message += ' (line ' + (error.line || error.lineNumber) + ')';
+ }
+
+ return message;
+ };
+
+ this.stack = function(error) {
+ return error ? error.stack : null;
+ };
+ }
+
+ return ExceptionFormatter;
+};
+
+getJasmineRequireObj().Expectation = function() {
+
+ function Expectation(options) {
+ this.util = options.util || { buildFailureMessage: function() {} };
+ this.customEqualityTesters = options.customEqualityTesters || [];
+ this.actual = options.actual;
+ this.addExpectationResult = options.addExpectationResult || function(){};
+ this.isNot = options.isNot;
+
+ var customMatchers = options.customMatchers || {};
+ for (var matcherName in customMatchers) {
+ this[matcherName] = Expectation.prototype.wrapCompare(matcherName, customMatchers[matcherName]);
+ }
+ }
+
+ Expectation.prototype.wrapCompare = function(name, matcherFactory) {
+ return function() {
+ var args = Array.prototype.slice.call(arguments, 0),
+ expected = args.slice(0),
+ message = '';
+
+ args.unshift(this.actual);
+
+ var matcher = matcherFactory(this.util, this.customEqualityTesters),
+ matcherCompare = matcher.compare;
+
+ function defaultNegativeCompare() {
+ var result = matcher.compare.apply(null, args);
+ result.pass = !result.pass;
+ return result;
+ }
+
+ if (this.isNot) {
+ matcherCompare = matcher.negativeCompare || defaultNegativeCompare;
+ }
+
+ var result = matcherCompare.apply(null, args);
+
+ if (!result.pass) {
+ if (!result.message) {
+ args.unshift(this.isNot);
+ args.unshift(name);
+ message = this.util.buildFailureMessage.apply(null, args);
+ } else {
+ if (Object.prototype.toString.apply(result.message) === '[object Function]') {
+ message = result.message();
+ } else {
+ message = result.message;
+ }
+ }
+ }
+
+ if (expected.length == 1) {
+ expected = expected[0];
+ }
+
+ // TODO: how many of these params are needed?
+ this.addExpectationResult(
+ result.pass,
+ {
+ matcherName: name,
+ passed: result.pass,
+ message: message,
+ actual: this.actual,
+ expected: expected // TODO: this may need to be arrayified/sliced
+ }
+ );
+ };
+ };
+
+ Expectation.addCoreMatchers = function(matchers) {
+ var prototype = Expectation.prototype;
+ for (var matcherName in matchers) {
+ var matcher = matchers[matcherName];
+ prototype[matcherName] = prototype.wrapCompare(matcherName, matcher);
+ }
+ };
+
+ Expectation.Factory = function(options) {
+ options = options || {};
+
+ var expect = new Expectation(options);
+
+ // TODO: this would be nice as its own Object - NegativeExpectation
+ // TODO: copy instead of mutate options
+ options.isNot = true;
+ expect.not = new Expectation(options);
+
+ return expect;
+ };
+
+ return Expectation;
+};
+
+//TODO: expectation result may make more sense as a presentation of an expectation.
+getJasmineRequireObj().buildExpectationResult = function() {
+ function buildExpectationResult(options) {
+ var messageFormatter = options.messageFormatter || function() {},
+ stackFormatter = options.stackFormatter || function() {};
+
+ var result = {
+ matcherName: options.matcherName,
+ message: message(),
+ stack: stack(),
+ passed: options.passed
+ };
+
+ if(!result.passed) {
+ result.expected = options.expected;
+ result.actual = options.actual;
+ }
+
+ return result;
+
+ function message() {
+ if (options.passed) {
+ return 'Passed.';
+ } else if (options.message) {
+ return options.message;
+ } else if (options.error) {
+ return messageFormatter(options.error);
+ }
+ return '';
+ }
+
+ function stack() {
+ if (options.passed) {
+ return '';
+ }
+
+ var error = options.error;
+ if (!error) {
+ try {
+ throw new Error(message());
+ } catch (e) {
+ error = e;
+ }
+ }
+ return stackFormatter(error);
+ }
+ }
+
+ return buildExpectationResult;
+};
+
+getJasmineRequireObj().MockDate = function() {
+ function MockDate(global) {
+ var self = this;
+ var currentTime = 0;
+
+ if (!global || !global.Date) {
+ self.install = function() {};
+ self.tick = function() {};
+ self.uninstall = function() {};
+ return self;
+ }
+
+ var GlobalDate = global.Date;
+
+ self.install = function(mockDate) {
+ if (mockDate instanceof GlobalDate) {
+ currentTime = mockDate.getTime();
+ } else {
+ currentTime = new GlobalDate().getTime();
+ }
+
+ global.Date = FakeDate;
+ };
+
+ self.tick = function(millis) {
+ millis = millis || 0;
+ currentTime = currentTime + millis;
+ };
+
+ self.uninstall = function() {
+ currentTime = 0;
+ global.Date = GlobalDate;
+ };
+
+ createDateProperties();
+
+ return self;
+
+ function FakeDate() {
+ switch(arguments.length) {
+ case 0:
+ return new GlobalDate(currentTime);
+ case 1:
+ return new GlobalDate(arguments[0]);
+ case 2:
+ return new GlobalDate(arguments[0], arguments[1]);
+ case 3:
+ return new GlobalDate(arguments[0], arguments[1], arguments[2]);
+ case 4:
+ return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3]);
+ case 5:
+ return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3],
+ arguments[4]);
+ case 6:
+ return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3],
+ arguments[4], arguments[5]);
+ default:
+ return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3],
+ arguments[4], arguments[5], arguments[6]);
+ }
+ }
+
+ function createDateProperties() {
+ FakeDate.prototype = GlobalDate.prototype;
+
+ FakeDate.now = function() {
+ if (GlobalDate.now) {
+ return currentTime;
+ } else {
+ throw new Error('Browser does not support Date.now()');
+ }
+ };
+
+ FakeDate.toSource = GlobalDate.toSource;
+ FakeDate.toString = GlobalDate.toString;
+ FakeDate.parse = GlobalDate.parse;
+ FakeDate.UTC = GlobalDate.UTC;
+ }
+ }
+
+ return MockDate;
+};
+
+getJasmineRequireObj().pp = function(j$) {
+
+ function PrettyPrinter() {
+ this.ppNestLevel_ = 0;
+ this.seen = [];
+ }
+
+ PrettyPrinter.prototype.format = function(value) {
+ this.ppNestLevel_++;
+ try {
+ if (j$.util.isUndefined(value)) {
+ this.emitScalar('undefined');
+ } else if (value === null) {
+ this.emitScalar('null');
+ } else if (value === 0 && 1/value === -Infinity) {
+ this.emitScalar('-0');
+ } else if (value === j$.getGlobal()) {
+ this.emitScalar('');
+ } else if (value.jasmineToString) {
+ this.emitScalar(value.jasmineToString());
+ } else if (typeof value === 'string') {
+ this.emitString(value);
+ } else if (j$.isSpy(value)) {
+ this.emitScalar('spy on ' + value.and.identity());
+ } else if (value instanceof RegExp) {
+ this.emitScalar(value.toString());
+ } else if (typeof value === 'function') {
+ this.emitScalar('Function');
+ } else if (typeof value.nodeType === 'number') {
+ this.emitScalar('HTMLNode');
+ } else if (value instanceof Date) {
+ this.emitScalar('Date(' + value + ')');
+ } else if (j$.util.arrayContains(this.seen, value)) {
+ this.emitScalar('');
+ } else if (j$.isArray_(value) || j$.isA_('Object', value)) {
+ this.seen.push(value);
+ if (j$.isArray_(value)) {
+ this.emitArray(value);
+ } else {
+ this.emitObject(value);
+ }
+ this.seen.pop();
+ } else {
+ this.emitScalar(value.toString());
+ }
+ } finally {
+ this.ppNestLevel_--;
+ }
+ };
+
+ PrettyPrinter.prototype.iterateObject = function(obj, fn) {
+ for (var property in obj) {
+ if (!Object.prototype.hasOwnProperty.call(obj, property)) { continue; }
+ fn(property, obj.__lookupGetter__ ? (!j$.util.isUndefined(obj.__lookupGetter__(property)) &&
+ obj.__lookupGetter__(property) !== null) : false);
+ }
+ };
+
+ PrettyPrinter.prototype.emitArray = j$.unimplementedMethod_;
+ PrettyPrinter.prototype.emitObject = j$.unimplementedMethod_;
+ PrettyPrinter.prototype.emitScalar = j$.unimplementedMethod_;
+ PrettyPrinter.prototype.emitString = j$.unimplementedMethod_;
+
+ function StringPrettyPrinter() {
+ PrettyPrinter.call(this);
+
+ this.string = '';
+ }
+
+ j$.util.inherit(StringPrettyPrinter, PrettyPrinter);
+
+ StringPrettyPrinter.prototype.emitScalar = function(value) {
+ this.append(value);
+ };
+
+ StringPrettyPrinter.prototype.emitString = function(value) {
+ this.append('\'' + value + '\'');
+ };
+
+ StringPrettyPrinter.prototype.emitArray = function(array) {
+ if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) {
+ this.append('Array');
+ return;
+ }
+ var length = Math.min(array.length, j$.MAX_PRETTY_PRINT_ARRAY_LENGTH);
+ this.append('[ ');
+ for (var i = 0; i < length; i++) {
+ if (i > 0) {
+ this.append(', ');
+ }
+ this.format(array[i]);
+ }
+ if(array.length > length){
+ this.append(', ...');
+ }
+
+ var self = this;
+ var first = array.length === 0;
+ this.iterateObject(array, function(property, isGetter) {
+ if (property.match(/^\d+$/)) {
+ return;
+ }
+
+ if (first) {
+ first = false;
+ } else {
+ self.append(', ');
+ }
+
+ self.formatProperty(array, property, isGetter);
+ });
+
+ this.append(' ]');
+ };
+
+ StringPrettyPrinter.prototype.emitObject = function(obj) {
+ var constructorName = obj.constructor ? j$.fnNameFor(obj.constructor) : 'null';
+ this.append(constructorName);
+
+ if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) {
+ return;
+ }
+
+ var self = this;
+ this.append('({ ');
+ var first = true;
+
+ this.iterateObject(obj, function(property, isGetter) {
+ if (first) {
+ first = false;
+ } else {
+ self.append(', ');
+ }
+
+ self.formatProperty(obj, property, isGetter);
+ });
+
+ this.append(' })');
+ };
+
+ StringPrettyPrinter.prototype.formatProperty = function(obj, property, isGetter) {
+ this.append(property);
+ this.append(': ');
+ if (isGetter) {
+ this.append('');
+ } else {
+ this.format(obj[property]);
+ }
+ };
+
+ StringPrettyPrinter.prototype.append = function(value) {
+ this.string += value;
+ };
+
+ return function(value) {
+ var stringPrettyPrinter = new StringPrettyPrinter();
+ stringPrettyPrinter.format(value);
+ return stringPrettyPrinter.string;
+ };
+};
+
+getJasmineRequireObj().QueueRunner = function(j$) {
+
+ function once(fn) {
+ var called = false;
+ return function() {
+ if (!called) {
+ called = true;
+ fn();
+ }
+ };
+ }
+
+ function QueueRunner(attrs) {
+ this.queueableFns = attrs.queueableFns || [];
+ this.onComplete = attrs.onComplete || function() {};
+ this.clearStack = attrs.clearStack || function(fn) {fn();};
+ this.onException = attrs.onException || function() {};
+ this.catchException = attrs.catchException || function() { return true; };
+ this.userContext = attrs.userContext || {};
+ this.timeout = attrs.timeout || {setTimeout: setTimeout, clearTimeout: clearTimeout};
+ this.fail = attrs.fail || function() {};
+ }
+
+ QueueRunner.prototype.execute = function() {
+ this.run(this.queueableFns, 0);
+ };
+
+ QueueRunner.prototype.run = function(queueableFns, recursiveIndex) {
+ var length = queueableFns.length,
+ self = this,
+ iterativeIndex;
+
+
+ for(iterativeIndex = recursiveIndex; iterativeIndex < length; iterativeIndex++) {
+ var queueableFn = queueableFns[iterativeIndex];
+ if (queueableFn.fn.length > 0) {
+ attemptAsync(queueableFn);
+ return;
+ } else {
+ attemptSync(queueableFn);
+ }
+ }
+
+ var runnerDone = iterativeIndex >= length;
+
+ if (runnerDone) {
+ this.clearStack(this.onComplete);
+ }
+
+ function attemptSync(queueableFn) {
+ try {
+ queueableFn.fn.call(self.userContext);
+ } catch (e) {
+ handleException(e, queueableFn);
+ }
+ }
+
+ function attemptAsync(queueableFn) {
+ var clearTimeout = function () {
+ Function.prototype.apply.apply(self.timeout.clearTimeout, [j$.getGlobal(), [timeoutId]]);
+ },
+ next = once(function () {
+ clearTimeout(timeoutId);
+ self.run(queueableFns, iterativeIndex + 1);
+ }),
+ timeoutId;
+
+ next.fail = function() {
+ self.fail.apply(null, arguments);
+ next();
+ };
+
+ if (queueableFn.timeout) {
+ timeoutId = Function.prototype.apply.apply(self.timeout.setTimeout, [j$.getGlobal(), [function() {
+ var error = new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.');
+ onException(error, queueableFn);
+ next();
+ }, queueableFn.timeout()]]);
+ }
+
+ try {
+ queueableFn.fn.call(self.userContext, next);
+ } catch (e) {
+ handleException(e, queueableFn);
+ next();
+ }
+ }
+
+ function onException(e, queueableFn) {
+ self.onException(e);
+ }
+
+ function handleException(e, queueableFn) {
+ onException(e, queueableFn);
+ if (!self.catchException(e)) {
+ //TODO: set a var when we catch an exception and
+ //use a finally block to close the loop in a nice way..
+ throw e;
+ }
+ }
+ };
+
+ return QueueRunner;
+};
+
+getJasmineRequireObj().ReportDispatcher = function() {
+ function ReportDispatcher(methods) {
+
+ var dispatchedMethods = methods || [];
+
+ for (var i = 0; i < dispatchedMethods.length; i++) {
+ var method = dispatchedMethods[i];
+ this[method] = (function(m) {
+ return function() {
+ dispatch(m, arguments);
+ };
+ }(method));
+ }
+
+ var reporters = [];
+
+ this.addReporter = function(reporter) {
+ reporters.push(reporter);
+ };
+
+ return this;
+
+ function dispatch(method, args) {
+ for (var i = 0; i < reporters.length; i++) {
+ var reporter = reporters[i];
+ if (reporter[method]) {
+ reporter[method].apply(reporter, args);
+ }
+ }
+ }
+ }
+
+ return ReportDispatcher;
+};
+
+
+getJasmineRequireObj().SpyRegistry = function(j$) {
+
+ function SpyRegistry(options) {
+ options = options || {};
+ var currentSpies = options.currentSpies || function() { return []; };
+
+ this.spyOn = function(obj, methodName) {
+ if (j$.util.isUndefined(obj)) {
+ throw new Error('spyOn could not find an object to spy upon for ' + methodName + '()');
+ }
+
+ if (j$.util.isUndefined(methodName)) {
+ throw new Error('No method name supplied');
+ }
+
+ if (j$.util.isUndefined(obj[methodName])) {
+ throw new Error(methodName + '() method does not exist');
+ }
+
+ if (obj[methodName] && j$.isSpy(obj[methodName])) {
+ //TODO?: should this return the current spy? Downside: may cause user confusion about spy state
+ throw new Error(methodName + ' has already been spied upon');
+ }
+
+ var spy = j$.createSpy(methodName, obj[methodName]);
+
+ currentSpies().push({
+ spy: spy,
+ baseObj: obj,
+ methodName: methodName,
+ originalValue: obj[methodName]
+ });
+
+ obj[methodName] = spy;
+
+ return spy;
+ };
+
+ this.clearSpies = function() {
+ var spies = currentSpies();
+ for (var i = 0; i < spies.length; i++) {
+ var spyEntry = spies[i];
+ spyEntry.baseObj[spyEntry.methodName] = spyEntry.originalValue;
+ }
+ };
+ }
+
+ return SpyRegistry;
+};
+
+getJasmineRequireObj().SpyStrategy = function() {
+
+ function SpyStrategy(options) {
+ options = options || {};
+
+ var identity = options.name || 'unknown',
+ originalFn = options.fn || function() {},
+ getSpy = options.getSpy || function() {},
+ plan = function() {};
+
+ this.identity = function() {
+ return identity;
+ };
+
+ this.exec = function() {
+ return plan.apply(this, arguments);
+ };
+
+ this.callThrough = function() {
+ plan = originalFn;
+ return getSpy();
+ };
+
+ this.returnValue = function(value) {
+ plan = function() {
+ return value;
+ };
+ return getSpy();
+ };
+
+ this.returnValues = function() {
+ var values = Array.prototype.slice.call(arguments);
+ plan = function () {
+ return values.shift();
+ };
+ return getSpy();
+ };
+
+ this.throwError = function(something) {
+ var error = (something instanceof Error) ? something : new Error(something);
+ plan = function() {
+ throw error;
+ };
+ return getSpy();
+ };
+
+ this.callFake = function(fn) {
+ plan = fn;
+ return getSpy();
+ };
+
+ this.stub = function(fn) {
+ plan = function() {};
+ return getSpy();
+ };
+ }
+
+ return SpyStrategy;
+};
+
+getJasmineRequireObj().Suite = function(j$) {
+ function Suite(attrs) {
+ this.env = attrs.env;
+ this.id = attrs.id;
+ this.parentSuite = attrs.parentSuite;
+ this.description = attrs.description;
+ this.expectationFactory = attrs.expectationFactory;
+ this.expectationResultFactory = attrs.expectationResultFactory;
+ this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure;
+
+ this.beforeFns = [];
+ this.afterFns = [];
+ this.beforeAllFns = [];
+ this.afterAllFns = [];
+ this.disabled = false;
+
+ this.children = [];
+
+ this.result = {
+ id: this.id,
+ description: this.description,
+ fullName: this.getFullName(),
+ failedExpectations: []
+ };
+ }
+
+ Suite.prototype.expect = function(actual) {
+ return this.expectationFactory(actual, this);
+ };
+
+ Suite.prototype.getFullName = function() {
+ var fullName = this.description;
+ for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) {
+ if (parentSuite.parentSuite) {
+ fullName = parentSuite.description + ' ' + fullName;
+ }
+ }
+ return fullName;
+ };
+
+ Suite.prototype.disable = function() {
+ this.disabled = true;
+ };
+
+ Suite.prototype.beforeEach = function(fn) {
+ this.beforeFns.unshift(fn);
+ };
+
+ Suite.prototype.beforeAll = function(fn) {
+ this.beforeAllFns.push(fn);
+ };
+
+ Suite.prototype.afterEach = function(fn) {
+ this.afterFns.unshift(fn);
+ };
+
+ Suite.prototype.afterAll = function(fn) {
+ this.afterAllFns.push(fn);
+ };
+
+ Suite.prototype.addChild = function(child) {
+ this.children.push(child);
+ };
+
+ Suite.prototype.status = function() {
+ if (this.disabled) {
+ return 'disabled';
+ }
+
+ if (this.result.failedExpectations.length > 0) {
+ return 'failed';
+ } else {
+ return 'finished';
+ }
+ };
+
+ Suite.prototype.isExecutable = function() {
+ return !this.disabled;
+ };
+
+ Suite.prototype.canBeReentered = function() {
+ return this.beforeAllFns.length === 0 && this.afterAllFns.length === 0;
+ };
+
+ Suite.prototype.getResult = function() {
+ this.result.status = this.status();
+ return this.result;
+ };
+
+ Suite.prototype.sharedUserContext = function() {
+ if (!this.sharedContext) {
+ this.sharedContext = this.parentSuite ? clone(this.parentSuite.sharedUserContext()) : {};
+ }
+
+ return this.sharedContext;
+ };
+
+ Suite.prototype.clonedSharedUserContext = function() {
+ return clone(this.sharedUserContext());
+ };
+
+ Suite.prototype.onException = function() {
+ if (arguments[0] instanceof j$.errors.ExpectationFailed) {
+ return;
+ }
+
+ if(isAfterAll(this.children)) {
+ var data = {
+ matcherName: '',
+ passed: false,
+ expected: '',
+ actual: '',
+ error: arguments[0]
+ };
+ this.result.failedExpectations.push(this.expectationResultFactory(data));
+ } else {
+ for (var i = 0; i < this.children.length; i++) {
+ var child = this.children[i];
+ child.onException.apply(child, arguments);
+ }
+ }
+ };
+
+ Suite.prototype.addExpectationResult = function () {
+ if(isAfterAll(this.children) && isFailure(arguments)){
+ var data = arguments[1];
+ this.result.failedExpectations.push(this.expectationResultFactory(data));
+ if(this.throwOnExpectationFailure) {
+ throw new j$.errors.ExpectationFailed();
+ }
+ } else {
+ for (var i = 0; i < this.children.length; i++) {
+ var child = this.children[i];
+ try {
+ child.addExpectationResult.apply(child, arguments);
+ } catch(e) {
+ // keep going
+ }
+ }
+ }
+ };
+
+ function isAfterAll(children) {
+ return children && children[0].result.status;
+ }
+
+ function isFailure(args) {
+ return !args[0];
+ }
+
+ function clone(obj) {
+ var clonedObj = {};
+ for (var prop in obj) {
+ if (obj.hasOwnProperty(prop)) {
+ clonedObj[prop] = obj[prop];
+ }
+ }
+
+ return clonedObj;
+ }
+
+ return Suite;
+};
+
+if (typeof window == void 0 && typeof exports == 'object') {
+ exports.Suite = jasmineRequire.Suite;
+}
+
+getJasmineRequireObj().Timer = function() {
+ var defaultNow = (function(Date) {
+ return function() { return new Date().getTime(); };
+ })(Date);
+
+ function Timer(options) {
+ options = options || {};
+
+ var now = options.now || defaultNow,
+ startTime;
+
+ this.start = function() {
+ startTime = now();
+ };
+
+ this.elapsed = function() {
+ return now() - startTime;
+ };
+ }
+
+ return Timer;
+};
+
+getJasmineRequireObj().TreeProcessor = function() {
+ function TreeProcessor(attrs) {
+ var tree = attrs.tree,
+ runnableIds = attrs.runnableIds,
+ queueRunnerFactory = attrs.queueRunnerFactory,
+ nodeStart = attrs.nodeStart || function() {},
+ nodeComplete = attrs.nodeComplete || function() {},
+ stats = { valid: true },
+ processed = false,
+ defaultMin = Infinity,
+ defaultMax = 1 - Infinity;
+
+ this.processTree = function() {
+ processNode(tree, false);
+ processed = true;
+ return stats;
+ };
+
+ this.execute = function(done) {
+ if (!processed) {
+ this.processTree();
+ }
+
+ if (!stats.valid) {
+ throw 'invalid order';
+ }
+
+ var childFns = wrapChildren(tree, 0);
+
+ queueRunnerFactory({
+ queueableFns: childFns,
+ userContext: tree.sharedUserContext(),
+ onException: function() {
+ tree.onException.apply(tree, arguments);
+ },
+ onComplete: done
+ });
+ };
+
+ function runnableIndex(id) {
+ for (var i = 0; i < runnableIds.length; i++) {
+ if (runnableIds[i] === id) {
+ return i;
+ }
+ }
+ }
+
+ function processNode(node, parentEnabled) {
+ var executableIndex = runnableIndex(node.id);
+
+ if (executableIndex !== undefined) {
+ parentEnabled = true;
+ }
+
+ parentEnabled = parentEnabled && node.isExecutable();
+
+ if (!node.children) {
+ stats[node.id] = {
+ executable: parentEnabled && node.isExecutable(),
+ segments: [{
+ index: 0,
+ owner: node,
+ nodes: [node],
+ min: startingMin(executableIndex),
+ max: startingMax(executableIndex)
+ }]
+ };
+ } else {
+ var hasExecutableChild = false;
+
+ for (var i = 0; i < node.children.length; i++) {
+ var child = node.children[i];
+
+ processNode(child, parentEnabled);
+
+ if (!stats.valid) {
+ return;
+ }
+
+ var childStats = stats[child.id];
+
+ hasExecutableChild = hasExecutableChild || childStats.executable;
+ }
+
+ stats[node.id] = {
+ executable: hasExecutableChild
+ };
+
+ segmentChildren(node, stats[node.id], executableIndex);
+
+ if (!node.canBeReentered() && stats[node.id].segments.length > 1) {
+ stats = { valid: false };
+ }
+ }
+ }
+
+ function startingMin(executableIndex) {
+ return executableIndex === undefined ? defaultMin : executableIndex;
+ }
+
+ function startingMax(executableIndex) {
+ return executableIndex === undefined ? defaultMax : executableIndex;
+ }
+
+ function segmentChildren(node, nodeStats, executableIndex) {
+ var currentSegment = { index: 0, owner: node, nodes: [], min: startingMin(executableIndex), max: startingMax(executableIndex) },
+ result = [currentSegment],
+ lastMax = defaultMax,
+ orderedChildSegments = orderChildSegments(node.children);
+
+ function isSegmentBoundary(minIndex) {
+ return lastMax !== defaultMax && minIndex !== defaultMin && lastMax < minIndex - 1;
+ }
+
+ for (var i = 0; i < orderedChildSegments.length; i++) {
+ var childSegment = orderedChildSegments[i],
+ maxIndex = childSegment.max,
+ minIndex = childSegment.min;
+
+ if (isSegmentBoundary(minIndex)) {
+ currentSegment = {index: result.length, owner: node, nodes: [], min: defaultMin, max: defaultMax};
+ result.push(currentSegment);
+ }
+
+ currentSegment.nodes.push(childSegment);
+ currentSegment.min = Math.min(currentSegment.min, minIndex);
+ currentSegment.max = Math.max(currentSegment.max, maxIndex);
+ lastMax = maxIndex;
+ }
+
+ nodeStats.segments = result;
+ }
+
+ function orderChildSegments(children) {
+ var specifiedOrder = [],
+ unspecifiedOrder = [];
+
+ for (var i = 0; i < children.length; i++) {
+ var child = children[i],
+ segments = stats[child.id].segments;
+
+ for (var j = 0; j < segments.length; j++) {
+ var seg = segments[j];
+
+ if (seg.min === defaultMin) {
+ unspecifiedOrder.push(seg);
+ } else {
+ specifiedOrder.push(seg);
+ }
+ }
+ }
+
+ specifiedOrder.sort(function(a, b) {
+ return a.min - b.min;
+ });
+
+ return specifiedOrder.concat(unspecifiedOrder);
+ }
+
+ function executeNode(node, segmentNumber) {
+ if (node.children) {
+ return {
+ fn: function(done) {
+ nodeStart(node);
+
+ queueRunnerFactory({
+ onComplete: function() {
+ nodeComplete(node, node.getResult());
+ done();
+ },
+ queueableFns: wrapChildren(node, segmentNumber),
+ userContext: node.sharedUserContext(),
+ onException: function() {
+ node.onException.apply(node, arguments);
+ }
+ });
+ }
+ };
+ } else {
+ return {
+ fn: function(done) { node.execute(done, stats[node.id].executable); }
+ };
+ }
+ }
+
+ function wrapChildren(node, segmentNumber) {
+ var result = [],
+ segmentChildren = stats[node.id].segments[segmentNumber].nodes;
+
+ for (var i = 0; i < segmentChildren.length; i++) {
+ result.push(executeNode(segmentChildren[i].owner, segmentChildren[i].index));
+ }
+
+ if (!stats[node.id].executable) {
+ return result;
+ }
+
+ return node.beforeAllFns.concat(result).concat(node.afterAllFns);
+ }
+ }
+
+ return TreeProcessor;
+};
+
+getJasmineRequireObj().Any = function(j$) {
+
+ function Any(expectedObject) {
+ this.expectedObject = expectedObject;
+ }
+
+ Any.prototype.asymmetricMatch = function(other) {
+ if (this.expectedObject == String) {
+ return typeof other == 'string' || other instanceof String;
+ }
+
+ if (this.expectedObject == Number) {
+ return typeof other == 'number' || other instanceof Number;
+ }
+
+ if (this.expectedObject == Function) {
+ return typeof other == 'function' || other instanceof Function;
+ }
+
+ if (this.expectedObject == Object) {
+ return typeof other == 'object';
+ }
+
+ if (this.expectedObject == Boolean) {
+ return typeof other == 'boolean';
+ }
+
+ return other instanceof this.expectedObject;
+ };
+
+ Any.prototype.jasmineToString = function() {
+ return '';
+ };
+
+ return Any;
+};
+
+getJasmineRequireObj().Anything = function(j$) {
+
+ function Anything() {}
+
+ Anything.prototype.asymmetricMatch = function(other) {
+ return !j$.util.isUndefined(other) && other !== null;
+ };
+
+ Anything.prototype.jasmineToString = function() {
+ return '';
+ };
+
+ return Anything;
+};
+
+getJasmineRequireObj().ArrayContaining = function(j$) {
+ function ArrayContaining(sample) {
+ this.sample = sample;
+ }
+
+ ArrayContaining.prototype.asymmetricMatch = function(other) {
+ var className = Object.prototype.toString.call(this.sample);
+ if (className !== '[object Array]') { throw new Error('You must provide an array to arrayContaining, not \'' + this.sample + '\'.'); }
+
+ for (var i = 0; i < this.sample.length; i++) {
+ var item = this.sample[i];
+ if (!j$.matchersUtil.contains(other, item)) {
+ return false;
+ }
+ }
+
+ return true;
+ };
+
+ ArrayContaining.prototype.jasmineToString = function () {
+ return '';
+ };
+
+ return ArrayContaining;
+};
+
+getJasmineRequireObj().ObjectContaining = function(j$) {
+
+ function ObjectContaining(sample) {
+ this.sample = sample;
+ }
+
+ function getPrototype(obj) {
+ if (Object.getPrototypeOf) {
+ return Object.getPrototypeOf(obj);
+ }
+
+ if (obj.constructor.prototype == obj) {
+ return null;
+ }
+
+ return obj.constructor.prototype;
+ }
+
+ function hasProperty(obj, property) {
+ if (!obj) {
+ return false;
+ }
+
+ if (Object.prototype.hasOwnProperty.call(obj, property)) {
+ return true;
+ }
+
+ return hasProperty(getPrototype(obj), property);
+ }
+
+ ObjectContaining.prototype.asymmetricMatch = function(other) {
+ if (typeof(this.sample) !== 'object') { throw new Error('You must provide an object to objectContaining, not \''+this.sample+'\'.'); }
+
+ for (var property in this.sample) {
+ if (!hasProperty(other, property) ||
+ !j$.matchersUtil.equals(this.sample[property], other[property])) {
+ return false;
+ }
+ }
+
+ return true;
+ };
+
+ ObjectContaining.prototype.jasmineToString = function() {
+ return '';
+ };
+
+ return ObjectContaining;
+};
+
+getJasmineRequireObj().StringMatching = function(j$) {
+
+ function StringMatching(expected) {
+ if (!j$.isString_(expected) && !j$.isA_('RegExp', expected)) {
+ throw new Error('Expected is not a String or a RegExp');
+ }
+
+ this.regexp = new RegExp(expected);
+ }
+
+ StringMatching.prototype.asymmetricMatch = function(other) {
+ return this.regexp.test(other);
+ };
+
+ StringMatching.prototype.jasmineToString = function() {
+ return '';
+ };
+
+ return StringMatching;
+};
+
+getJasmineRequireObj().errors = function() {
+ function ExpectationFailed() {}
+
+ ExpectationFailed.prototype = new Error();
+ ExpectationFailed.prototype.constructor = ExpectationFailed;
+
+ return {
+ ExpectationFailed: ExpectationFailed
+ };
+};
+getJasmineRequireObj().matchersUtil = function(j$) {
+ // TODO: what to do about jasmine.pp not being inject? move to JSON.stringify? gut PrettyPrinter?
+
+ return {
+ equals: function(a, b, customTesters) {
+ customTesters = customTesters || [];
+
+ return eq(a, b, [], [], customTesters);
+ },
+
+ contains: function(haystack, needle, customTesters) {
+ customTesters = customTesters || [];
+
+ if ((Object.prototype.toString.apply(haystack) === '[object Array]') ||
+ (!!haystack && !haystack.indexOf))
+ {
+ for (var i = 0; i < haystack.length; i++) {
+ if (eq(haystack[i], needle, [], [], customTesters)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ return !!haystack && haystack.indexOf(needle) >= 0;
+ },
+
+ buildFailureMessage: function() {
+ var args = Array.prototype.slice.call(arguments, 0),
+ matcherName = args[0],
+ isNot = args[1],
+ actual = args[2],
+ expected = args.slice(3),
+ englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); });
+
+ var message = 'Expected ' +
+ j$.pp(actual) +
+ (isNot ? ' not ' : ' ') +
+ englishyPredicate;
+
+ if (expected.length > 0) {
+ for (var i = 0; i < expected.length; i++) {
+ if (i > 0) {
+ message += ',';
+ }
+ message += ' ' + j$.pp(expected[i]);
+ }
+ }
+
+ return message + '.';
+ }
+ };
+
+ function isAsymmetric(obj) {
+ return obj && j$.isA_('Function', obj.asymmetricMatch);
+ }
+
+ function asymmetricMatch(a, b) {
+ var asymmetricA = isAsymmetric(a),
+ asymmetricB = isAsymmetric(b);
+
+ if (asymmetricA && asymmetricB) {
+ return undefined;
+ }
+
+ if (asymmetricA) {
+ return a.asymmetricMatch(b);
+ }
+
+ if (asymmetricB) {
+ return b.asymmetricMatch(a);
+ }
+ }
+
+ // Equality function lovingly adapted from isEqual in
+ // [Underscore](http://underscorejs.org)
+ function eq(a, b, aStack, bStack, customTesters) {
+ var result = true;
+
+ var asymmetricResult = asymmetricMatch(a, b);
+ if (!j$.util.isUndefined(asymmetricResult)) {
+ return asymmetricResult;
+ }
+
+ for (var i = 0; i < customTesters.length; i++) {
+ var customTesterResult = customTesters[i](a, b);
+ if (!j$.util.isUndefined(customTesterResult)) {
+ return customTesterResult;
+ }
+ }
+
+ if (a instanceof Error && b instanceof Error) {
+ return a.message == b.message;
+ }
+
+ // Identical objects are equal. `0 === -0`, but they aren't identical.
+ // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
+ if (a === b) { return a !== 0 || 1 / a == 1 / b; }
+ // A strict comparison is necessary because `null == undefined`.
+ if (a === null || b === null) { return a === b; }
+ var className = Object.prototype.toString.call(a);
+ if (className != Object.prototype.toString.call(b)) { return false; }
+ switch (className) {
+ // Strings, numbers, dates, and booleans are compared by value.
+ case '[object String]':
+ // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
+ // equivalent to `new String("5")`.
+ return a == String(b);
+ case '[object Number]':
+ // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
+ // other numeric values.
+ return a != +a ? b != +b : (a === 0 ? 1 / a == 1 / b : a == +b);
+ case '[object Date]':
+ case '[object Boolean]':
+ // Coerce dates and booleans to numeric primitive values. Dates are compared by their
+ // millisecond representations. Note that invalid dates with millisecond representations
+ // of `NaN` are not equivalent.
+ return +a == +b;
+ // RegExps are compared by their source patterns and flags.
+ case '[object RegExp]':
+ return a.source == b.source &&
+ a.global == b.global &&
+ a.multiline == b.multiline &&
+ a.ignoreCase == b.ignoreCase;
+ }
+ if (typeof a != 'object' || typeof b != 'object') { return false; }
+
+ var aIsDomNode = j$.isDomNode(a);
+ var bIsDomNode = j$.isDomNode(b);
+ if (aIsDomNode && bIsDomNode) {
+ // At first try to use DOM3 method isEqualNode
+ if (a.isEqualNode) {
+ return a.isEqualNode(b);
+ }
+ // IE8 doesn't support isEqualNode, try to use outerHTML && innerText
+ var aIsElement = a instanceof Element;
+ var bIsElement = b instanceof Element;
+ if (aIsElement && bIsElement) {
+ return a.outerHTML == b.outerHTML;
+ }
+ if (aIsElement || bIsElement) {
+ return false;
+ }
+ return a.innerText == b.innerText && a.textContent == b.textContent;
+ }
+ if (aIsDomNode || bIsDomNode) {
+ return false;
+ }
+
+ // Assume equality for cyclic structures. The algorithm for detecting cyclic
+ // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
+ var length = aStack.length;
+ while (length--) {
+ // Linear search. Performance is inversely proportional to the number of
+ // unique nested structures.
+ if (aStack[length] == a) { return bStack[length] == b; }
+ }
+ // Add the first object to the stack of traversed objects.
+ aStack.push(a);
+ bStack.push(b);
+ var size = 0;
+ // Recursively compare objects and arrays.
+ // Compare array lengths to determine if a deep comparison is necessary.
+ if (className == '[object Array]' && a.length !== b.length) {
+ result = false;
+ }
+
+ if (result) {
+ // Objects with different constructors are not equivalent, but `Object`s
+ // or `Array`s from different frames are.
+ if (className !== '[object Array]') {
+ var aCtor = a.constructor, bCtor = b.constructor;
+ if (aCtor !== bCtor && !(isFunction(aCtor) && aCtor instanceof aCtor &&
+ isFunction(bCtor) && bCtor instanceof bCtor)) {
+ return false;
+ }
+ }
+ // Deep compare objects.
+ for (var key in a) {
+ if (has(a, key)) {
+ // Count the expected number of properties.
+ size++;
+ // Deep compare each member.
+ if (!(result = has(b, key) && eq(a[key], b[key], aStack, bStack, customTesters))) { break; }
+ }
+ }
+ // Ensure that both objects contain the same number of properties.
+ if (result) {
+ for (key in b) {
+ if (has(b, key) && !(size--)) { break; }
+ }
+ result = !size;
+ }
+ }
+ // Remove the first object from the stack of traversed objects.
+ aStack.pop();
+ bStack.pop();
+
+ return result;
+
+ function has(obj, key) {
+ return Object.prototype.hasOwnProperty.call(obj, key);
+ }
+
+ function isFunction(obj) {
+ return typeof obj === 'function';
+ }
+ }
+};
+
+getJasmineRequireObj().toBe = function() {
+ function toBe() {
+ return {
+ compare: function(actual, expected) {
+ return {
+ pass: actual === expected
+ };
+ }
+ };
+ }
+
+ return toBe;
+};
+
+getJasmineRequireObj().toBeCloseTo = function() {
+
+ function toBeCloseTo() {
+ return {
+ compare: function(actual, expected, precision) {
+ if (precision !== 0) {
+ precision = precision || 2;
+ }
+
+ return {
+ pass: Math.abs(expected - actual) < (Math.pow(10, -precision) / 2)
+ };
+ }
+ };
+ }
+
+ return toBeCloseTo;
+};
+
+getJasmineRequireObj().toBeDefined = function() {
+ function toBeDefined() {
+ return {
+ compare: function(actual) {
+ return {
+ pass: (void 0 !== actual)
+ };
+ }
+ };
+ }
+
+ return toBeDefined;
+};
+
+getJasmineRequireObj().toBeFalsy = function() {
+ function toBeFalsy() {
+ return {
+ compare: function(actual) {
+ return {
+ pass: !!!actual
+ };
+ }
+ };
+ }
+
+ return toBeFalsy;
+};
+
+getJasmineRequireObj().toBeGreaterThan = function() {
+
+ function toBeGreaterThan() {
+ return {
+ compare: function(actual, expected) {
+ return {
+ pass: actual > expected
+ };
+ }
+ };
+ }
+
+ return toBeGreaterThan;
+};
+
+
+getJasmineRequireObj().toBeLessThan = function() {
+ function toBeLessThan() {
+ return {
+
+ compare: function(actual, expected) {
+ return {
+ pass: actual < expected
+ };
+ }
+ };
+ }
+
+ return toBeLessThan;
+};
+getJasmineRequireObj().toBeNaN = function(j$) {
+
+ function toBeNaN() {
+ return {
+ compare: function(actual) {
+ var result = {
+ pass: (actual !== actual)
+ };
+
+ if (result.pass) {
+ result.message = 'Expected actual not to be NaN.';
+ } else {
+ result.message = function() { return 'Expected ' + j$.pp(actual) + ' to be NaN.'; };
+ }
+
+ return result;
+ }
+ };
+ }
+
+ return toBeNaN;
+};
+
+getJasmineRequireObj().toBeNull = function() {
+
+ function toBeNull() {
+ return {
+ compare: function(actual) {
+ return {
+ pass: actual === null
+ };
+ }
+ };
+ }
+
+ return toBeNull;
+};
+
+getJasmineRequireObj().toBeTruthy = function() {
+
+ function toBeTruthy() {
+ return {
+ compare: function(actual) {
+ return {
+ pass: !!actual
+ };
+ }
+ };
+ }
+
+ return toBeTruthy;
+};
+
+getJasmineRequireObj().toBeUndefined = function() {
+
+ function toBeUndefined() {
+ return {
+ compare: function(actual) {
+ return {
+ pass: void 0 === actual
+ };
+ }
+ };
+ }
+
+ return toBeUndefined;
+};
+
+getJasmineRequireObj().toContain = function() {
+ function toContain(util, customEqualityTesters) {
+ customEqualityTesters = customEqualityTesters || [];
+
+ return {
+ compare: function(actual, expected) {
+
+ return {
+ pass: util.contains(actual, expected, customEqualityTesters)
+ };
+ }
+ };
+ }
+
+ return toContain;
+};
+
+getJasmineRequireObj().toEqual = function() {
+
+ function toEqual(util, customEqualityTesters) {
+ customEqualityTesters = customEqualityTesters || [];
+
+ return {
+ compare: function(actual, expected) {
+ var result = {
+ pass: false
+ };
+
+ result.pass = util.equals(actual, expected, customEqualityTesters);
+
+ return result;
+ }
+ };
+ }
+
+ return toEqual;
+};
+
+getJasmineRequireObj().toHaveBeenCalled = function(j$) {
+
+ function toHaveBeenCalled() {
+ return {
+ compare: function(actual) {
+ var result = {};
+
+ if (!j$.isSpy(actual)) {
+ throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.');
+ }
+
+ if (arguments.length > 1) {
+ throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith');
+ }
+
+ result.pass = actual.calls.any();
+
+ result.message = result.pass ?
+ 'Expected spy ' + actual.and.identity() + ' not to have been called.' :
+ 'Expected spy ' + actual.and.identity() + ' to have been called.';
+
+ return result;
+ }
+ };
+ }
+
+ return toHaveBeenCalled;
+};
+
+getJasmineRequireObj().toHaveBeenCalledWith = function(j$) {
+
+ function toHaveBeenCalledWith(util, customEqualityTesters) {
+ return {
+ compare: function() {
+ var args = Array.prototype.slice.call(arguments, 0),
+ actual = args[0],
+ expectedArgs = args.slice(1),
+ result = { pass: false };
+
+ if (!j$.isSpy(actual)) {
+ throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.');
+ }
+
+ if (!actual.calls.any()) {
+ result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but it was never called.'; };
+ return result;
+ }
+
+ if (util.contains(actual.calls.allArgs(), expectedArgs, customEqualityTesters)) {
+ result.pass = true;
+ result.message = function() { return 'Expected spy ' + actual.and.identity() + ' not to have been called with ' + j$.pp(expectedArgs) + ' but it was.'; };
+ } else {
+ result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but actual calls were ' + j$.pp(actual.calls.allArgs()).replace(/^\[ | \]$/g, '') + '.'; };
+ }
+
+ return result;
+ }
+ };
+ }
+
+ return toHaveBeenCalledWith;
+};
+
+getJasmineRequireObj().toMatch = function(j$) {
+
+ function toMatch() {
+ return {
+ compare: function(actual, expected) {
+ if (!j$.isString_(expected) && !j$.isA_('RegExp', expected)) {
+ throw new Error('Expected is not a String or a RegExp');
+ }
+
+ var regexp = new RegExp(expected);
+
+ return {
+ pass: regexp.test(actual)
+ };
+ }
+ };
+ }
+
+ return toMatch;
+};
+
+getJasmineRequireObj().toThrow = function(j$) {
+
+ function toThrow(util) {
+ return {
+ compare: function(actual, expected) {
+ var result = { pass: false },
+ threw = false,
+ thrown;
+
+ if (typeof actual != 'function') {
+ throw new Error('Actual is not a Function');
+ }
+
+ try {
+ actual();
+ } catch (e) {
+ threw = true;
+ thrown = e;
+ }
+
+ if (!threw) {
+ result.message = 'Expected function to throw an exception.';
+ return result;
+ }
+
+ if (arguments.length == 1) {
+ result.pass = true;
+ result.message = function() { return 'Expected function not to throw, but it threw ' + j$.pp(thrown) + '.'; };
+
+ return result;
+ }
+
+ if (util.equals(thrown, expected)) {
+ result.pass = true;
+ result.message = function() { return 'Expected function not to throw ' + j$.pp(expected) + '.'; };
+ } else {
+ result.message = function() { return 'Expected function to throw ' + j$.pp(expected) + ', but it threw ' + j$.pp(thrown) + '.'; };
+ }
+
+ return result;
+ }
+ };
+ }
+
+ return toThrow;
+};
+
+getJasmineRequireObj().toThrowError = function(j$) {
+ function toThrowError (util) {
+ return {
+ compare: function(actual) {
+ var threw = false,
+ pass = {pass: true},
+ fail = {pass: false},
+ thrown;
+
+ if (typeof actual != 'function') {
+ throw new Error('Actual is not a Function');
+ }
+
+ var errorMatcher = getMatcher.apply(null, arguments);
+
+ try {
+ actual();
+ } catch (e) {
+ threw = true;
+ thrown = e;
+ }
+
+ if (!threw) {
+ fail.message = 'Expected function to throw an Error.';
+ return fail;
+ }
+
+ if (!(thrown instanceof Error)) {
+ fail.message = function() { return 'Expected function to throw an Error, but it threw ' + j$.pp(thrown) + '.'; };
+ return fail;
+ }
+
+ if (errorMatcher.hasNoSpecifics()) {
+ pass.message = 'Expected function not to throw an Error, but it threw ' + j$.fnNameFor(thrown) + '.';
+ return pass;
+ }
+
+ if (errorMatcher.matches(thrown)) {
+ pass.message = function() {
+ return 'Expected function not to throw ' + errorMatcher.errorTypeDescription + errorMatcher.messageDescription() + '.';
+ };
+ return pass;
+ } else {
+ fail.message = function() {
+ return 'Expected function to throw ' + errorMatcher.errorTypeDescription + errorMatcher.messageDescription() +
+ ', but it threw ' + errorMatcher.thrownDescription(thrown) + '.';
+ };
+ return fail;
+ }
+ }
+ };
+
+ function getMatcher() {
+ var expected = null,
+ errorType = null;
+
+ if (arguments.length == 2) {
+ expected = arguments[1];
+ if (isAnErrorType(expected)) {
+ errorType = expected;
+ expected = null;
+ }
+ } else if (arguments.length > 2) {
+ errorType = arguments[1];
+ expected = arguments[2];
+ if (!isAnErrorType(errorType)) {
+ throw new Error('Expected error type is not an Error.');
+ }
+ }
+
+ if (expected && !isStringOrRegExp(expected)) {
+ if (errorType) {
+ throw new Error('Expected error message is not a string or RegExp.');
+ } else {
+ throw new Error('Expected is not an Error, string, or RegExp.');
+ }
+ }
+
+ function messageMatch(message) {
+ if (typeof expected == 'string') {
+ return expected == message;
+ } else {
+ return expected.test(message);
+ }
+ }
+
+ return {
+ errorTypeDescription: errorType ? j$.fnNameFor(errorType) : 'an exception',
+ thrownDescription: function(thrown) {
+ var thrownName = errorType ? j$.fnNameFor(thrown.constructor) : 'an exception',
+ thrownMessage = '';
+
+ if (expected) {
+ thrownMessage = ' with message ' + j$.pp(thrown.message);
+ }
+
+ return thrownName + thrownMessage;
+ },
+ messageDescription: function() {
+ if (expected === null) {
+ return '';
+ } else if (expected instanceof RegExp) {
+ return ' with a message matching ' + j$.pp(expected);
+ } else {
+ return ' with message ' + j$.pp(expected);
+ }
+ },
+ hasNoSpecifics: function() {
+ return expected === null && errorType === null;
+ },
+ matches: function(error) {
+ return (errorType === null || error instanceof errorType) &&
+ (expected === null || messageMatch(error.message));
+ }
+ };
+ }
+
+ function isStringOrRegExp(potential) {
+ return potential instanceof RegExp || (typeof potential == 'string');
+ }
+
+ function isAnErrorType(type) {
+ if (typeof type !== 'function') {
+ return false;
+ }
+
+ var Surrogate = function() {};
+ Surrogate.prototype = type.prototype;
+ return (new Surrogate()) instanceof Error;
+ }
+ }
+
+ return toThrowError;
+};
+
+getJasmineRequireObj().interface = function(jasmine, env) {
+ var jasmineInterface = {
+ describe: function(description, specDefinitions) {
+ return env.describe(description, specDefinitions);
+ },
+
+ xdescribe: function(description, specDefinitions) {
+ return env.xdescribe(description, specDefinitions);
+ },
+
+ fdescribe: function(description, specDefinitions) {
+ return env.fdescribe(description, specDefinitions);
+ },
+
+ it: function() {
+ return env.it.apply(env, arguments);
+ },
+
+ xit: function() {
+ return env.xit.apply(env, arguments);
+ },
+
+ fit: function() {
+ return env.fit.apply(env, arguments);
+ },
+
+ beforeEach: function() {
+ return env.beforeEach.apply(env, arguments);
+ },
+
+ afterEach: function() {
+ return env.afterEach.apply(env, arguments);
+ },
+
+ beforeAll: function() {
+ return env.beforeAll.apply(env, arguments);
+ },
+
+ afterAll: function() {
+ return env.afterAll.apply(env, arguments);
+ },
+
+ expect: function(actual) {
+ return env.expect(actual);
+ },
+
+ pending: function() {
+ return env.pending.apply(env, arguments);
+ },
+
+ fail: function() {
+ return env.fail.apply(env, arguments);
+ },
+
+ spyOn: function(obj, methodName) {
+ return env.spyOn(obj, methodName);
+ },
+
+ jsApiReporter: new jasmine.JsApiReporter({
+ timer: new jasmine.Timer()
+ }),
+
+ jasmine: jasmine
+ };
+
+ jasmine.addCustomEqualityTester = function(tester) {
+ env.addCustomEqualityTester(tester);
+ };
+
+ jasmine.addMatchers = function(matchers) {
+ return env.addMatchers(matchers);
+ };
+
+ jasmine.clock = function() {
+ return env.clock;
+ };
+
+ return jasmineInterface;
+};
+
+getJasmineRequireObj().version = function() {
+ return '2.3.4';
+};
diff --git a/src/assets/js/vendor/vendor.js b/src/assets/js/vendor/vendor.js
index a290e9c..a3ca9d3 100644
--- a/src/assets/js/vendor/vendor.js
+++ b/src/assets/js/vendor/vendor.js
@@ -38228,757 +38228,3227 @@
!window.angular.$$csp().noInlineStyle && window.angular.element(document.head).prepend('');
/**
- * @license AngularJS v1.4.8
+ * @license AngularJS v1.4.7
* (c) 2010-2015 Google, Inc. http://angularjs.org
* License: MIT
*/
-(function(window, angular, undefined) {'use strict';
+(function (window, angular, undefined) {
- var $resourceMinErr = angular.$$minErr('$resource');
+ 'use strict';
-// Helper functions and regex to lookup a dotted path on an object
-// stopping at undefined/null. The path must be composed of ASCII
-// identifiers (just like $parse)
- var MEMBER_NAME_REGEX = /^(\.[a-zA-Z_$@][0-9a-zA-Z_$@]*)+$/;
+ /**
+ * @ngdoc object
+ * @name angular.mock
+ * @description
+ *
+ * Namespace from 'angular-mocks.js' which contains testing related code.
+ */
+ angular.mock = {};
- function isValidDottedPath(path) {
- return (path != null && path !== '' && path !== 'hasOwnProperty' &&
- MEMBER_NAME_REGEX.test('.' + path));
- }
+ /**
+ * ! 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();
+ };
+ };
- function lookupDottedPath(obj, path) {
- if (!isValidDottedPath(path)) {
- throw $resourceMinErr('badmember', 'Dotted member path "@{0}" is invalid.', path);
- }
- var keys = path.split('.');
- for (var i = 0, ii = keys.length; i < ii && angular.isDefined(obj); i++) {
- var key = keys[i];
- obj = (obj !== null) ? obj[key] : undefined;
- }
- return obj;
- }
+ angular.mock.$Browser = function () {
+ var self = this;
- /**
- * Create a shallow copy of an object and clear other fields from the destination
- */
- function shallowClearAndCopy(src, dst) {
- dst = dst || {};
+ this.isMock = true;
+ self.$$url = "http://server/";
+ self.$$lastUrl = self.$$url; // used by url polling fn
+ self.pollFns = [];
- angular.forEach(dst, function(value, key) {
- delete dst[key];
- });
+ // TODO(vojta): remove this temporary api
+ self.$$completeOutstandingRequest = angular.noop;
+ self.$$incOutstandingRequestCount = angular.noop;
- for (var key in src) {
- if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) {
- dst[key] = src[key];
- }
- }
- return dst;
- }
+ // register url polling fn
- /**
- * @ngdoc module
- * @name ngResource
- * @description
- *
- * # ngResource
- *
- * The `ngResource` module provides interaction support with RESTful services
- * via the $resource service.
- *
- *
- *
- *
- * See {@link ngResource.$resource `$resource`} for usage.
- */
+ 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);
+ }
+ }
+ );
- /**
- * @ngdoc service
- * @name $resource
- * @requires $http
- *
- * @description
- * A factory which creates a resource object that lets you interact with
- * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources.
- *
- * The returned resource object has action methods which provide high-level behaviors without
- * the need to interact with the low level {@link ng.$http $http} service.
- *
- * Requires the {@link ngResource `ngResource`} module to be installed.
- *
- * By default, trailing slashes will be stripped from the calculated URLs,
- * which can pose problems with server backends that do not expect that
- * behavior. This can be disabled by configuring the `$resourceProvider` like
- * this:
- *
- * ```js
- app.config(['$resourceProvider', function($resourceProvider) {
- // Don't strip trailing slashes from calculated URLs
- $resourceProvider.defaults.stripTrailingSlashes = false;
- }]);
- * ```
- *
- * @param {string} url A parameterized URL template with parameters prefixed by `:` as in
- * `/user/:username`. If you are using a URL with a port number (e.g.
- * `http://example.com:8080/api`), it will be respected.
- *
- * If you are using a url with a suffix, just add the suffix, like this:
- * `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')`
- * or even `$resource('http://example.com/resource/:resource_id.:format')`
- * If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be
- * collapsed down to a single `.`. If you need this sequence to appear and not collapse then you
- * can escape it with `/\.`.
- *
- * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
- * `actions` methods. If any of the parameter value is a function, it will be executed every time
- * when a param value needs to be obtained for a request (unless the param was overridden).
- *
- * Each key value in the parameter object is first bound to url template if present and then any
- * excess keys are appended to the url search query after the `?`.
- *
- * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in
- * URL `/path/greet?salutation=Hello`.
- *
- * If the parameter value is prefixed with `@` then the value for that parameter will be extracted
- * from the corresponding property on the `data` object (provided when calling an action method). For
- * example, if the `defaultParam` object is `{someParam: '@someProp'}` then the value of `someParam`
- * will be `data.someProp`.
- *
- * @param {Object.