<commit_before>module.exports = function(grunt) {
    'use strict';

    // Project configuration.
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        copy: {
            dist: {
                files: [
                    {expand: true, src: ['Controller/*'], dest: 'dist/', filter: 'isFile'},
                    {expand: true, src: ['package.json'], dest: 'dist/'},
                    {expand: true, src: ['bootstrap.js'], dest: 'dist/'},
                    {expand: true, src: ['LICENSE'], dest: 'dist/'},
                    {expand: true, src: ['README.md'], dest: 'dist/'}
                ]
            }
        },
        clean: {
            dist: ['dist/']
        },
        nodeunit: {
            all: ['Tests/*.js']
        },
        yuidoc: {
            compile: {
                name: '<%= pkg.name %>',
                description: '<%= pkg.description %>',
                version: '<%= pkg.version %>',
                options: {
                    paths: [
                        "dist"
                    ],
                    outdir: 'Docs/'
                }
            }
        }
    });

    grunt.loadNpmTasks('grunt-contrib-copy');
    grunt.loadNpmTasks('grunt-contrib-clean');
    grunt.loadNpmTasks('grunt-contrib-nodeunit');
    grunt.loadNpmTasks('grunt-contrib-yuidoc');

    grunt.registerTask('default', ['nodeunit:all', 'clean:dist', 'copy:dist']);
    grunt.registerTask('doc', ['clean:dist', 'copy:dist', 'yuidoc']);
    grunt.registerTask('test', ['nodeunit']);
};<commit_msg>Added missing files to copy to dist/ folder with grunt<commit_after>module.exports = function(grunt) {
    'use strict';

    // Project configuration.
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        copy: {
            dist: {
                files: [
                    {expand: true, src: ['Controller/*'], dest: 'dist/', filter: 'isFile'},
                    {expand: true, src: ['Lib/**/*'],     dest: 'dist/'},
                    {expand: true, src: ['package.json'], dest: 'dist/'},
                    {expand: true, src: ['bootstrap.js'], dest: 'dist/'},
                    {expand: true, src: ['LICENSE'],      dest: 'dist/'},
                    {expand: true, src: ['README.md'],    dest: 'dist/'}
                ]
            }
        },
        clean: {
            dist: ['dist/']
        },
        nodeunit: {
            all: ['Tests/*.js']
        },
        yuidoc: {
            compile: {
                name: '<%= pkg.name %>',
                description: '<%= pkg.description %>',
                version: '<%= pkg.version %>',
                options: {
                    paths: [
                        "dist"
                    ],
                    outdir: 'Docs/'
                }
            }
        }
    });

    grunt.loadNpmTasks('grunt-contrib-copy');
    grunt.loadNpmTasks('grunt-contrib-clean');
    grunt.loadNpmTasks('grunt-contrib-nodeunit');
    grunt.loadNpmTasks('grunt-contrib-yuidoc');

    grunt.registerTask('default', ['nodeunit:all', 'clean:dist', 'copy:dist']);
    grunt.registerTask('doc', ['clean:dist', 'copy:dist', 'yuidoc']);
    grunt.registerTask('test', ['nodeunit']);
};<|endoftext|>


<commit_before>var assert = require('chai').assert;
var jsdom = require('mocha-jsdom');

var MyPlugin = function() {
    return this;
}

MyPlugin.prototype.getSource = function() {
    return this.source;
}

describe('Boost JS', function () {

    jsdom()

    before(function () {
        $ = require('jquery')
        boost = require('../src/boost.js')
    });

    describe('plugin', function () {

        it('should be attached to jquery prototype', function () {
            $.fn.myplugin = boost(MyPlugin);
            assert.isDefined( $.fn.myplugin );
        });

        it('should throw error if no function is provided', function () {
            assert.throws(function(){boost();});
        });

    });

    describe('init', function () {

        it('should be accessible from jquery instance', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin);
            var inst = $('div').myplugin();
            assert.match( inst.source[0].nodeName, /DIV/i );
        });

        it('should also be accessible from jquery prototype', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin);
            var inst = $.fn.myplugin.init( document.querySelectorAll('div'), {} );
            assert.match( inst.source[0].nodeName, /DIV/i );
        });

        it('should inherit prototype methods from "MyPlugin"', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin);
            var inst = $('div').myplugin();
            assert.equal( inst.getSource(), inst.source  );
        });

    });

    describe('auto', function () {

        it('should init with valid [data-init] attr', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin);
            boost.auto();
            assert.lengthOf( Object.keys($.fn.myplugin.instances), 1 );
        });

        it('should init with hypenated/camelCased plugin name', function () {
            document.body.innerHTML = '

';
            $.fn.somePlugin = boost(MyPlugin);
            boost.auto();
            assert.lengthOf( Object.keys($.fn.somePlugin.instances), 2 );
        });

        it('should NOT init with unknown plugin name', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin);
            boost.auto();
            assert.lengthOf( Object.keys($.fn.myplugin.instances), 0 );
        });

    });

    describe('source', function () {

        it('should be a jquery object and match source element', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin);
            var inst = $('div').myplugin();
            assert.match( inst.source[0].nodeName, /DIV/i );
        });

    });

    describe('id', function () {

        it('should exist', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin);
            var inst = $('div').myplugin();
            assert.isDefined( inst.id );
        });

        it('should match html attribute if present', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin);
            var inst = $('div').myplugin();
            assert.match( inst.id, /foo-bar/ );
        });

        it('should be empty string if html attribute doesn\'t exist', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin);
            var inst = $('div').myplugin();
            assert.equals( inst.id, null );
        });

    });

    describe('settings', function () {

        it('should be able to be defined at plugin creation (defaults)', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin, {foo:'bar'});
            var inst = $('div').myplugin();
            assert.match( inst.settings.foo, /bar/ );
        });

        it('should be able to be updated at instantiation', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin,{foo:'bar'});
            var inst = $('div').myplugin({foo:'foo'});
            assert.match( inst.settings.foo, /foo/ );
        });

        it('should be able to be updated by data attribute', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin,{foo:'bar'});
            var inst = $('div').myplugin();
            assert.match( inst.settings.foo, /foo/ );
        });

    });

    describe('dataset', function () {

        it('should be converted to boolean if value is "true"', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin, {foo:false});
            var inst = $('div').myplugin();
            assert.isBoolean( inst.settings.foo );
            assert.isTrue( inst.settings.foo );
        });

        it('should be converted to boolean if value is "false"', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin, {foo:true});
            var inst = $('div').myplugin();
            assert.isBoolean( inst.settings.foo );
            assert.isFalse( inst.settings.foo );
        });

        it('should be converted to integer if value is a whole number', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin);
            var inst = $('div').myplugin();
            assert.isNumber( inst.settings.foo, 10 );
        });

        it('should be converted to float if value is a decimal number', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin);
            var inst = $('div').myplugin();
            assert.isNumber( inst.settings.foo, .5 );
        });

    });

    describe('references', function () {

        it('should collect elements with data-bind="#[id]"', function () {
            document.body.innerHTML = '

<button data-bind="#foo"></button>';
            $.fn.myplugin = boost(MyPlugin);
            var inst = $('div').myplugin();
            assert.lengthOf( inst.references, 1 );
            assert.match( inst.references[0].nodeName, /BUTTON/i );
        });

        it('should collect elements with href="#[id]"', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin);
            var inst = $('div').myplugin();
            assert.lengthOf( inst.references, 1 );
            assert.match( inst.references[0].nodeName, /A/i );
        });

        it('should NOT collect elements without hash before id', function () {
            document.body.innerHTML = '

<button data-bind="foo"></button>';
            $.fn.myplugin = boost(MyPlugin);
            var inst = $('div').myplugin();
            assert.lengthOf( inst.references, 0 );
        });

        it('should NOT collect elements with no id', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin);
            var inst = $('div').myplugin();
            assert.lengthOf( inst.references, 0 );
        });

    });

    describe('roles', function () {

        it('should organize references by "data-role" attributes', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin);
            var inst = $('div').myplugin();
            assert.lengthOf( inst.references, 2 );
            assert.isDefined( inst.roles.foo );
            assert.lengthOf( inst.roles.foo, 1 );
            assert.match( inst.roles.foo[0].nodeName, /A/i );
            assert.isDefined( inst.roles.bar );
            assert.lengthOf( inst.roles.bar, 1 );
            assert.match( inst.roles.bar[0].nodeName, /A/i );
        });

    });

    describe('instances', function () {

        it('should be accessible from jquery prototype', function () {
            assert.isDefined($.fn.myplugin.instances);
        });

        it('should use camel-cased id as key if id exists', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin);
            var inst = $('div').myplugin();
            assert.isDefined($.fn.myplugin.instances.fooBar);
            assert.isDefined($.fn.myplugin.instances.fooBar.source);
        });

        it('should use integer as key if no id exists', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin);
            var inst = $('div').myplugin();
            assert.isDefined($.fn.myplugin.instances[0]);
            assert.isDefined($.fn.myplugin.instances[0].source);
        });

    });

});
<commit_msg>updated test<commit_after>var assert = require('chai').assert;
var jsdom = require('mocha-jsdom');

var MyPlugin = function() {
    return this;
}

MyPlugin.prototype.getSource = function() {
    return this.source;
}

describe('Boost JS', function () {

    jsdom()

    before(function () {
        $ = require('jquery')
        boost = require('../src/boost.js')
    });

    describe('plugin', function () {

        it('should be attached to jquery prototype', function () {
            $.fn.myplugin = boost(MyPlugin);
            assert.isDefined( $.fn.myplugin );
        });

        it('should throw error if no function is provided', function () {
            assert.throws(function(){boost();});
        });

    });

    describe('init', function () {

        it('should be accessible from jquery instance', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin);
            var inst = $('div').myplugin();
            assert.match( inst.source[0].nodeName, /DIV/i );
        });

        it('should also be accessible from jquery prototype', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin);
            var inst = $.fn.myplugin.init( document.querySelectorAll('div'), {} );
            assert.match( inst.source[0].nodeName, /DIV/i );
        });

        it('should inherit prototype methods from "MyPlugin"', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin);
            var inst = $('div').myplugin();
            assert.equal( inst.getSource(), inst.source  );
        });

    });

    describe('auto', function () {

        it('should init with valid [data-init] attr', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin);
            boost.auto();
            assert.lengthOf( Object.keys($.fn.myplugin.instances), 1 );
        });

        it('should init with hypenated/camelCased plugin name', function () {
            document.body.innerHTML = '

';
            $.fn.somePlugin = boost(MyPlugin);
            boost.auto();
            assert.lengthOf( Object.keys($.fn.somePlugin.instances), 2 );
        });

        it('should NOT init with unknown plugin name', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin);
            boost.auto();
            assert.lengthOf( Object.keys($.fn.myplugin.instances), 0 );
        });

    });

    describe('source', function () {

        it('should be a jquery object and match source element', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin);
            var inst = $('div').myplugin();
            assert.match( inst.source[0].nodeName, /DIV/i );
        });

    });

    describe('id', function () {

        it('should exist', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin);
            var inst = $('div').myplugin();
            assert.isDefined( inst.id );
        });

        it('should match html attribute if present', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin);
            var inst = $('div').myplugin();
            assert.match( inst.id, /foo-bar/ );
        });

        it('should be empty string if html attribute doesn\'t exist', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin);
            var inst = $('div').myplugin();
            assert.equal( inst.id, null );
        });

    });

    describe('settings', function () {

        it('should be able to be defined at plugin creation (defaults)', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin, {foo:'bar'});
            var inst = $('div').myplugin();
            assert.match( inst.settings.foo, /bar/ );
        });

        it('should be able to be updated at instantiation', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin,{foo:'bar'});
            var inst = $('div').myplugin({foo:'foo'});
            assert.match( inst.settings.foo, /foo/ );
        });

        it('should be able to be updated by data attribute', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin,{foo:'bar'});
            var inst = $('div').myplugin();
            assert.match( inst.settings.foo, /foo/ );
        });

    });

    describe('dataset', function () {

        it('should be converted to boolean if value is "true"', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin, {foo:false});
            var inst = $('div').myplugin();
            assert.isBoolean( inst.settings.foo );
            assert.isTrue( inst.settings.foo );
        });

        it('should be converted to boolean if value is "false"', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin, {foo:true});
            var inst = $('div').myplugin();
            assert.isBoolean( inst.settings.foo );
            assert.isFalse( inst.settings.foo );
        });

        it('should be converted to integer if value is a whole number', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin);
            var inst = $('div').myplugin();
            assert.isNumber( inst.settings.foo, 10 );
        });

        it('should be converted to float if value is a decimal number', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin);
            var inst = $('div').myplugin();
            assert.isNumber( inst.settings.foo, .5 );
        });

    });

    describe('references', function () {

        it('should collect elements with data-bind="#[id]"', function () {
            document.body.innerHTML = '

<button data-bind="#foo"></button>';
            $.fn.myplugin = boost(MyPlugin);
            var inst = $('div').myplugin();
            assert.lengthOf( inst.references, 1 );
            assert.match( inst.references[0].nodeName, /BUTTON/i );
        });

        it('should collect elements with href="#[id]"', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin);
            var inst = $('div').myplugin();
            assert.lengthOf( inst.references, 1 );
            assert.match( inst.references[0].nodeName, /A/i );
        });

        it('should NOT collect elements without hash before id', function () {
            document.body.innerHTML = '

<button data-bind="foo"></button>';
            $.fn.myplugin = boost(MyPlugin);
            var inst = $('div').myplugin();
            assert.lengthOf( inst.references, 0 );
        });

        it('should NOT collect elements with no id', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin);
            var inst = $('div').myplugin();
            assert.lengthOf( inst.references, 0 );
        });

    });

    describe('roles', function () {

        it('should organize references by "data-role" attributes', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin);
            var inst = $('div').myplugin();
            assert.lengthOf( inst.references, 2 );
            assert.isDefined( inst.roles.foo );
            assert.lengthOf( inst.roles.foo, 1 );
            assert.match( inst.roles.foo[0].nodeName, /A/i );
            assert.isDefined( inst.roles.bar );
            assert.lengthOf( inst.roles.bar, 1 );
            assert.match( inst.roles.bar[0].nodeName, /A/i );
        });

    });

    describe('instances', function () {

        it('should be accessible from jquery prototype', function () {
            assert.isDefined($.fn.myplugin.instances);
        });

        it('should use camel-cased id as key if id exists', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin);
            var inst = $('div').myplugin();
            assert.isDefined($.fn.myplugin.instances.fooBar);
            assert.isDefined($.fn.myplugin.instances.fooBar.source);
        });

        it('should use integer as key if no id exists', function () {
            document.body.innerHTML = '

';
            $.fn.myplugin = boost(MyPlugin);
            var inst = $('div').myplugin();
            assert.isDefined($.fn.myplugin.instances[0]);
            assert.isDefined($.fn.myplugin.instances[0].source);
        });

    });

});
<|endoftext|>


<commit_before>'use strict';

const fs = require('fs');
const path = require('path');
const uglify = require('uglify-js');
const uglifyify = require('uglifyify');
const util = require('../util');

module.exports = applyUglifyify;

/**
 * Adds the Uglifyify transform to the given Browserify or Watchify instance.
 *
 * Minification is done in two phases, both using UglifyJS:
 *
 *  PHASE 1 - The first phase occurs as a Browserify transform, which minifies each module
 *            individually. This allows Uglify to eliminate dead code paths within each module.
 *
 *  PHASE 2 - The second phase occurs after Browserify is finished, and minifies the entire
 *            bundle file. This allows Uglify to achieve the smallest possible file size.
 *
 * @param {Browserify} browserify - The Browserify or Watchify instance
 * @param {object} [options] - The Uglifyify options, if any
 */
function applyUglifyify (browserify, options) {
  options = options || {};

  if (options.global === undefined) {
    // Apply uglifyify to ALL modules in the bundle, even third-party ones
    options.global = true;
  }

  if (options.exts === undefined) {
    // Uglify can minify JavaScript and JSON files
    options.exts = ['.js', '.json'];
  }

  if (options.output === undefined) {
    options.output = {
      // Keep important comments when minifying
      comments: /^!|^\*!|@preserve|@license|@cc_on/,
    };
  }

  // PHASE 1 - Minify each module individually
  browserify.transform(uglifyify, options);

  // Change the options a bit for PHASE 2
  let postProcessingOptions = util.cloneObj(options);
  let bundleFile = path.resolve(browserify.files.outputFile);
  let mapFile;

  // Don't mangle again, since that was already done in PHASE 1
  postProcessingOptions.mangle = false;
  postProcessingOptions.mangleProperties = false;

  // If the bundle has a sourcemap, then UglifyJS needs to modify it
  if (browserify.files.mapFile) {
    mapFile = path.resolve(browserify.files.mapFile);
    postProcessingOptions.inSourceMap = mapFile;
    postProcessingOptions.outSourceMap = mapFile;
    postProcessingOptions.sourceMapUrl = path.basename(mapFile);
  }

  // PHASE 2 - Minify the entire bundle file
  browserify.postProcessing = function () {
    return phase2()
      .then(overwriteBundle)
      .then(overwriteSourcemap);
  };

  // Minify the entire bundle using UglifyJS
  function phase2 () {
    return new Promise(resolve => {
      let minified = uglify.minify(bundleFile, postProcessingOptions);
      resolve(minified);
    });
  }

  // Overwrite the bundle file from PHASE 1
  function overwriteBundle (minified) {
    return new Promise((resolve, reject) => {
      fs.writeFile(bundleFile, minified.code, err => err ? reject(err) : resolve(minified));
    });
  }

  // Overwrite the sourcemap file from PHASE 1
  function overwriteSourcemap (minified) {
    return new Promise((resolve, reject) => {
      if (!mapFile) {
        // There is no sourcemap, so we don't need to do anything
        return resolve();
      }

      // Replace the absolute path with the relative path
      let map = JSON.parse(minified.map);
      map.file = path.basename(bundleFile);

      // Overwrite the file
      fs.writeFile(mapFile, JSON.stringify(map), err => err ? reject(err) : resolve(minified));
    });
  }
}
<commit_msg>No need to perform additional mangling, compressing, comment-removal, etc. during the second-pass of UglifyJS<commit_after>'use strict';

const fs = require('fs');
const path = require('path');
const uglify = require('uglify-js');
const uglifyify = require('uglifyify');

module.exports = applyUglifyify;

/**
 * Adds the Uglifyify transform to the given Browserify or Watchify instance.
 *
 * Minification is done in two phases, both using UglifyJS:
 *
 *  PHASE 1 - The first phase occurs as a Browserify transform, which minifies each module
 *            individually. This allows Uglify to eliminate dead code paths within each module.
 *
 *  PHASE 2 - The second phase occurs after Browserify is finished, and minifies the entire
 *            bundle file. This allows Uglify to achieve the smallest possible file size.
 *
 * @param {Browserify} browserify - The Browserify or Watchify instance
 * @param {object} [options] - The Uglifyify options, if any
 */
function applyUglifyify (browserify, options) {
  options = options || {};
  let bundlePath = path.resolve(browserify.files.outputFile);
  let sourcemapPath = browserify.files.mapFile && path.resolve(browserify.files.mapFile);

  if (options.global === undefined) {
    // Apply uglifyify to ALL modules in the bundle, even third-party ones
    options.global = true;
  }

  if (options.exts === undefined) {
    // Uglify can minify JavaScript and JSON files
    options.exts = ['.js', '.json'];
  }

  if (options.output === undefined) {
    options.output = {
      // Keep important comments when minifying
      comments: /^!|^\*!|@preserve|@license|@cc_on/,
    };
  }

  // PHASE 1 - Minify each module individually
  browserify.transform(uglifyify, options);

  // PHASE 2 - Minify the entire bundle file
  browserify.postProcessing = function () {
    return phase2()
      .then(overwriteBundle)
      .then(overwriteSourcemap);
  };

  // Minify the entire bundle using UglifyJS
  function phase2 () {
    return new Promise(resolve => {
      // For PHASE 2, we don't need to perform additional mangling, compressing,
      // comment-removal, etc., since that has already been done in PHASE 1
      options = {
        mangle: false,
        mangleProperties: false,
        compress: false,
        output: {
          comments: true,
        },
      };

      // If the bundle has a sourcemap, then UglifyJS needs to modify it
      if (browserify.files.mapFile) {
        options.inSourceMap = sourcemapPath;
        options.outSourceMap = sourcemapPath;
        options.sourceMapUrl = path.basename(sourcemapPath);
      }

      let minified = uglify.minify(bundlePath, options);
      resolve(minified);
    });
  }

  // Overwrite the bundle file from PHASE 1
  function overwriteBundle (minified) {
    return new Promise((resolve, reject) => {
      fs.writeFile(bundlePath, minified.code, err => err ? reject(err) : resolve(minified));
    });
  }

  // Overwrite the sourcemap file from PHASE 1
  function overwriteSourcemap (minified) {
    return new Promise((resolve, reject) => {
      if (!browserify.files.mapFile) {
        // There is no sourcemap, so we don't need to do anything
        return resolve();
      }

      // Replace the absolute path with the relative path
      let map = JSON.parse(minified.map);
      map.file = path.basename(bundlePath);

      // Overwrite the file
      fs.writeFile(sourcemapPath, JSON.stringify(map), err => err ? reject(err) : resolve(minified));
    });
  }
}
<|endoftext|>


<commit_before>import { setInputList, updateInputList, getParentValue } from './tools';
import {
    expectValue,
    haveContractsForSymbol,
    getContractsAvailableForSymbol,
    getDurationsForContracts,
    getBarriersForContracts,
    getPredictionForContracts,
} from '../shared';
import { insideTrade } from '../../relationChecker';
import { findTopParentBlock, hideInteractionsFromBlockly, getBlocksByType } from '../../utils';
import { translate } from '../../../../../common/i18n';
import { observer as globalObserver } from '../../../../../common/utils/observer';
import config from '../../../../common/const';

export default () => {
    Blockly.Blocks.tradeOptions = {
        init: function init() {
            setInputList(this);
            this.setPreviousStatement(true, 'TradeOptions');
            this.setColour('#f2f2f2');
        },
        onchange: function onchange(ev) {
            insideTrade(this, ev, translate('Trade Options'));
            if (ev.group === 'BackwardCompatibility') {
                return;
            }
            if (ev.type === Blockly.Events.CREATE) {
                updateInputList(this);
                const block = Blockly.mainWorkspace.getBlockById(ev.blockId);
                if (block && block.type === 'trade' && ev.workspaceId === Blockly.mainWorkspace.id) {
                    const symbol = block.getFieldValue('SYMBOL_LIST');
                    if (!symbol) return;

                    this.pollForContracts(symbol).then(contracts => {
                        this.updateBarrierOffsetBlocks(contracts, false, false); // false false to maintain user's values on import
                        this.updatePredictionBlocks(contracts);
                        this.updateDurationLists(contracts, false, false); // false false to maintain user's values on import
                    });
                }
            } else if (ev.type === Blockly.Events.MOVE) {
                // Make sure tradeOptions-blocks are consistent with symbol/tradeType when re-added to root-block
                if (!ev.oldParentId && ev.newParentId) {
                    const movedBlock = Blockly.mainWorkspace.getBlockById(ev.blockId);
                    const topParentBlock = findTopParentBlock(movedBlock);

                    if (topParentBlock && topParentBlock.type === 'trade') {
                        const symbol = topParentBlock.getFieldValue('SYMBOL_LIST');
                        if (!symbol) return;

                        const getNestedTradeOptions = block => {
                            if (block.type === 'tradeOptions') {
                                this.pollForContracts(symbol).then(contracts => {
                                    this.updateBarrierOffsetBlocks(contracts, false, false, [block.id]);
                                    this.applyBarrierHandlebars('BARRIEROFFSETTYPE_LIST', [ev.blockId], true);
                                    this.updatePredictionBlocks(contracts, [block.id]);
                                    this.updateDurationLists(contracts, false, false, [block.id]);
                                });
                            }
                            block.getChildren().forEach(childBlock => {
                                getNestedTradeOptions(childBlock);
                            });
                        };
                        getNestedTradeOptions(movedBlock);
                    }
                }
            } else if (ev.type === Blockly.Events.CHANGE) {
                // eslint-disable-next-line no-underscore-dangle
                if (this.parentBlock_ !== null) {
                    const symbol = getParentValue(this, 'SYMBOL_LIST');
                    if (!symbol) return;

                    this.pollForContracts(symbol).then(contracts => {
                        if (ev.name === 'SYMBOL_LIST' && ev.oldValue !== ev.newValue) {
                            // Called to update duration options and set min durations
                            this.updateDurationLists(contracts, true, true);
                        } else if (['TRADETYPE_LIST'].includes(ev.name) && ev.oldValue !== ev.newValue) {
                            // Both are called to check if these blocks are required
                            this.updatePredictionBlocks(contracts);
                            this.updateBarrierOffsetBlocks(contracts, true, true);
                            this.applyBarrierHandlebars('BARRIEROFFSETTYPE_LIST', [ev.blockId], true);
                            // Called to default to smallest durations for symbol
                            this.updateDurationLists(contracts, false, true);
                        } else if (ev.name === 'DURATIONTYPE_LIST' && ev.oldValue !== ev.newValue) {
                            // Called to set barriers based on duration
                            this.updateBarrierOffsetBlocks(contracts, true, true, [ev.blockId]);
                            this.applyBarrierHandlebars('BARRIEROFFSETTYPE_LIST', [ev.blockId], true);
                            // Called to set min durations for selected unit
                            this.updateDurationLists(contracts, false, true, [ev.blockId]);
                        } else if (
                            ['BARRIEROFFSETTYPE_LIST', 'SECONDBARRIEROFFSETTYPE_LIST'].includes(ev.name) &&
                            ev.oldValue !== ev.newValue
                        ) {
                            // Called to set default values based on barrier type
                            this.updateBarrierOffsetBlocks(contracts, false, true, [ev.blockId]);
                            // Suggests same type barriers
                            this.applyBarrierHandlebars(ev.name, [ev.blockId], false);
                        }
                        updateInputList(this);
                    });
                }
            }
        },
        pollForContracts(symbol) {
            return new Promise(resolve => {
                const contractsForSymbol = haveContractsForSymbol(symbol);
                if (!contractsForSymbol) {
                    // Register an event and use as a lock to avoid spamming API
                    const event = `contractsLoaded.${symbol}`;
                    if (!globalObserver.isRegistered(event)) {
                        globalObserver.register(event, () => {});
                        getContractsAvailableForSymbol(symbol).then(contracts => {
                            globalObserver.unregisterAll(event); // Release the lock
                            resolve(contracts);
                        });
                    } else {
                        // Request in progress, start polling localStorage until contracts are available.
                        const pollingFn = setInterval(() => {
                            const contracts = haveContractsForSymbol(symbol);
                            if (contracts) {
                                clearInterval(pollingFn);
                                resolve(contracts.available);
                            }
                        }, 100);
                        setTimeout(() => {
                            clearInterval(pollingFn);
                            resolve([]);
                        }, 10000);
                    }
                } else {
                    resolve(contractsForSymbol.available);
                }
            });
        },
        updatePredictionBlocks(contracts, updateOnly = []) {
            getBlocksByType('tradeOptions').forEach(tradeOptionsBlock => {
                if (tradeOptionsBlock.disabled) return;
                if (updateOnly.length && !updateOnly.includes(tradeOptionsBlock.id)) return;

                const predictionInput = tradeOptionsBlock.getInput('PREDICTION');
                if (!predictionInput) return;

                const tradeType = getParentValue(tradeOptionsBlock, 'TRADETYPE_LIST');
                const predictionRange = getPredictionForContracts(contracts, tradeType);

                hideInteractionsFromBlockly(() => {
                    if (!predictionRange.length) {
                        tradeOptionsBlock.removeInput('PREDICTION');
                        return;
                    }
                    predictionInput.setVisible(true);

                    // Attach shadow block with API-returned prediction-value (only if user hasn't defined a value)
                    if (!predictionInput.connection.isConnected()) {
                        predictionInput.attachShadowBlock(predictionRange[0], 'NUM', 'math_number');
                    }
                });
            });
        },
        updateBarrierOffsetBlocks(contracts, useDefaultType = false, setDefaultValue = false, updateOnly = []) {
            getBlocksByType('tradeOptions').forEach(tradeOptionsBlock => {
                if (tradeOptionsBlock.disabled) return;
                if (updateOnly.length && !updateOnly.includes(tradeOptionsBlock.id)) return;

                const tradeType = getParentValue(tradeOptionsBlock, 'TRADETYPE_LIST');
                const selectedDuration = tradeOptionsBlock.getFieldValue('DURATIONTYPE_LIST');
                const selectedBarrierTypes = [
                    tradeOptionsBlock.getFieldValue('BARRIEROFFSETTYPE_LIST'),
                    tradeOptionsBlock.getFieldValue('SECONDBARRIEROFFSETTYPE_LIST'),
                ];
                const barriers = getBarriersForContracts(contracts, tradeType, selectedDuration, selectedBarrierTypes);

                hideInteractionsFromBlockly(() => {
                    const revealBarrierBlock = (barrierValue, inputName) => {
                        const barrierInput = tradeOptionsBlock.getInput(inputName);
                        if (barrierInput) {
                            barrierInput.setVisible(true);
                            if (!barrierInput.connection.isConnected()) {
                                barrierInput.attachShadowBlock(barrierValue, 'NUM', 'math_number');
                            } else if (setDefaultValue) {
                                const connectedBlock = barrierInput.connection.targetBlock();
                                if (connectedBlock.isShadow()) {
                                    connectedBlock.setFieldValue(barrierValue, 'NUM');
                                }
                            }
                        }
                    };

                    const barrierOffsetNames = ['BARRIEROFFSET', 'SECONDBARRIEROFFSET'];
                    const barrierLabels = [translate('High barrier'), translate('Low barrier')];
                    const removeInput = inputName => tradeOptionsBlock.removeInput(inputName);

                    const updateList = (list, options) => {
                        const prevSelectedType = options.find(option => option[1] === list.getValue());
                        list.menuGenerator_ = options; // eslint-disable-line no-underscore-dangle, no-param-reassign
                        if (!useDefaultType && prevSelectedType) {
                            list.setValue('');
                            list.setValue(prevSelectedType[1]);
                            // eslint-disable-next-line no-underscore-dangle
                        } else if (list.menuGenerator_.length) {
                            list.setValue('');
                            list.setValue(list.menuGenerator_[0][1]); // eslint-disable-line no-underscore-dangle
                        }
                    };

                    if (!barriers.values.length) {
                        barrierOffsetNames.forEach(removeInput);
                        return;
                    }

                    barriers.values.forEach((barrierValue, index) => {
                        const typeList = tradeOptionsBlock.getField(`${barrierOffsetNames[index]}TYPE_LIST`);
                        const typeInput = tradeOptionsBlock.getInput(barrierOffsetNames[index]);
                        const absoluteType = [[translate('Absolute'), 'absolute']];

                        if (selectedDuration === 'd') {
                            updateList(typeList, absoluteType);
                        } else if (barriers.allowBothTypes || barriers.allowAbsoluteType) {
                            updateList(typeList, [...config.barrierTypes, ...absoluteType]);
                        } else {
                            updateList(typeList, config.barrierTypes);
                        }

                        if (barriers.values.length === 1) {
                            typeInput.fieldRow[0].setText(`${translate('Barrier')}:`);
                        } else {
                            typeInput.fieldRow[0].setText(`${barrierLabels[index]}:`);
                        }
                        revealBarrierBlock(barrierValue, barrierOffsetNames[index]);
                    });
                    barrierOffsetNames.slice(barriers.values.length).forEach(removeInput);
                });
            });
        },
        updateDurationLists(contracts, useDefaultUnit = false, setMinDuration = false, updateOnly = []) {
            getBlocksByType('tradeOptions').forEach(tradeOptionsBlock => {
                if (tradeOptionsBlock.disabled) return;
                if (updateOnly.length && !updateOnly.includes(tradeOptionsBlock.id)) return;

                const tradeType = getParentValue(tradeOptionsBlock, 'TRADETYPE_LIST');
                const durationTypeList = tradeOptionsBlock.getField('DURATIONTYPE_LIST');
                const selectedDuration = durationTypeList.getValue();

                const durations = getDurationsForContracts(contracts, tradeType);
                const durationOptions = durations.map(duration => [duration.label, duration.unit]);

                hideInteractionsFromBlockly(() => {
                    // Prevent UI flickering by only updating field only if options have changed
                    // eslint-disable-next-line no-underscore-dangle
                    if (JSON.stringify(durationTypeList.menuGenerator_) !== JSON.stringify(durationOptions)) {
                        durationTypeList.menuGenerator_ = durationOptions; // eslint-disable-line no-underscore-dangle
                    }

                    // Set duration to previous selected duration (required for imported strategies)
                    // eslint-disable-next-line no-underscore-dangle
                    const prevSelectedDuration = durationTypeList.menuGenerator_.find(d => d[1] === selectedDuration);
                    if (!useDefaultUnit && prevSelectedDuration) {
                        durationTypeList.setValue('');
                        durationTypeList.setValue(prevSelectedDuration[1]);
                        // eslint-disable-next-line no-underscore-dangle
                    } else if (durationTypeList.menuGenerator_.length) {
                        durationTypeList.setValue('');
                        // eslint-disable-next-line no-underscore-dangle
                        durationTypeList.setValue(durationTypeList.menuGenerator_[0][1]);
                    }

                    // Attach shadow block with min value (only when user hasn't already attached another output block)
                    if (durations.length) {
                        const durationInput = tradeOptionsBlock.getInput('DURATION');
                        if (!durationInput.connection.isConnected()) {
                            durationInput.attachShadowBlock(durations[0].minimum, 'NUM', 'math_number');
                        } else if (setMinDuration) {
                            const connectedBlock = durationInput.connection.targetBlock();
                            const minDuration = durations.find(d => d.unit === selectedDuration);

                            if (connectedBlock.isShadow() && minDuration) {
                                connectedBlock.setFieldValue(minDuration.minimum, 'NUM');
                            }
                        }
                    }
                });
            });
        },
        applyBarrierHandlebars(barrierFieldName, blockId, forceDistinct = false) {
            const block = Blockly.mainWorkspace.getBlockById(blockId);
            const newValue = block.getFieldValue(barrierFieldName);

            const otherBarrierListName = () => {
                if (barrierFieldName === 'BARRIEROFFSETTYPE_LIST') {
                    return 'SECONDBARRIEROFFSETTYPE_LIST';
                }
                return 'BARRIEROFFSETTYPE_LIST';
            };

            const otherBarrierList = block.getField(otherBarrierListName());
            if (otherBarrierList) {
                const otherBarrierType = otherBarrierList.getValue();
                if (
                    config.barrierTypes.findIndex(t => t[1] === newValue) !== -1 &&
                    (otherBarrierType === 'absolute' || forceDistinct)
                ) {
                    const otherValue = config.barrierTypes.find(t => t[1] !== newValue);
                    otherBarrierList.setValue(otherValue[1]);
                } else if (newValue === 'absolute' && otherBarrierType !== 'absolute') {
                    otherBarrierList.setValue('absolute');
                }
            }
        },
    };
    Blockly.JavaScript.tradeOptions = block => {
        const tradeDefBlock = findTopParentBlock(block);
        if (!tradeDefBlock) {
            return '';
        }

        const durationValue = expectValue(block, 'DURATION');
        const durationType = block.getFieldValue('DURATIONTYPE_LIST');
        const currency = block.getFieldValue('CURRENCY_LIST');
        const amount = expectValue(block, 'AMOUNT');

        const isVisibleField = field => block.getInput(field) && block.getInput(field).isVisible();

        let predictionValue = 'undefined';
        let barrierOffsetValue = 'undefined';
        let secondBarrierOffsetValue = 'undefined';

        if (isVisibleField('PREDICTION')) {
            predictionValue = expectValue(block, 'PREDICTION');
        }
        if (isVisibleField('BARRIEROFFSET')) {
            const barrierOffsetType = block.getFieldValue('BARRIEROFFSETTYPE_LIST');
            const value = expectValue(block, 'BARRIEROFFSET');
            barrierOffsetValue = barrierOffsetType === 'absolute' ? `${value}` : `'${barrierOffsetType}${value}'`;
        }
        if (isVisibleField('SECONDBARRIEROFFSET')) {
            const barrierOffsetType = block.getFieldValue('SECONDBARRIEROFFSETTYPE_LIST');
            const value = expectValue(block, 'SECONDBARRIEROFFSET');
            secondBarrierOffsetValue = barrierOffsetType === 'absolute' ? `${value}` : `'${barrierOffsetType}${value}'`;
        }

        const code = `
            Bot.start({
            limitations: BinaryBotPrivateLimitations,
            duration: ${durationValue},
            duration_unit: '${durationType}',
            currency: '${currency}',
            amount: ${amount},
            prediction: ${predictionValue},
            barrierOffset: ${barrierOffsetValue},
            secondBarrierOffset: ${secondBarrierOffsetValue},
            });
        `;
        return code;
    };
};
<commit_msg>Support barrier handlebars for multiple and individual tradeOptions blocks<commit_after>import { setInputList, updateInputList, getParentValue } from './tools';
import {
    expectValue,
    haveContractsForSymbol,
    getContractsAvailableForSymbol,
    getDurationsForContracts,
    getBarriersForContracts,
    getPredictionForContracts,
} from '../shared';
import { insideTrade } from '../../relationChecker';
import { findTopParentBlock, hideInteractionsFromBlockly, getBlocksByType } from '../../utils';
import { translate } from '../../../../../common/i18n';
import { observer as globalObserver } from '../../../../../common/utils/observer';
import config from '../../../../common/const';

export default () => {
    Blockly.Blocks.tradeOptions = {
        init: function init() {
            setInputList(this);
            this.setPreviousStatement(true, 'TradeOptions');
            this.setColour('#f2f2f2');
        },
        onchange: function onchange(ev) {
            insideTrade(this, ev, translate('Trade Options'));
            if (ev.group === 'BackwardCompatibility') {
                return;
            }
            if (ev.type === Blockly.Events.CREATE) {
                updateInputList(this);
                const block = Blockly.mainWorkspace.getBlockById(ev.blockId);
                if (block && block.type === 'trade' && ev.workspaceId === Blockly.mainWorkspace.id) {
                    const symbol = block.getFieldValue('SYMBOL_LIST');
                    if (!symbol) return;

                    this.pollForContracts(symbol).then(contracts => {
                        this.updateBarrierOffsetBlocks(contracts, false, false); // false false to maintain user's values on import
                        this.updatePredictionBlocks(contracts);
                        this.updateDurationLists(contracts, false, false); // false false to maintain user's values on import
                    });
                }
            } else if (ev.type === Blockly.Events.MOVE) {
                // Make sure tradeOptions-blocks are consistent with symbol/tradeType when re-added to root-block
                if (!ev.oldParentId && ev.newParentId) {
                    const movedBlock = Blockly.mainWorkspace.getBlockById(ev.blockId);
                    const topParentBlock = findTopParentBlock(movedBlock);

                    if (topParentBlock && topParentBlock.type === 'trade') {
                        const symbol = topParentBlock.getFieldValue('SYMBOL_LIST');
                        if (!symbol) return;

                        const getNestedTradeOptions = block => {
                            if (block.type === 'tradeOptions') {
                                this.pollForContracts(symbol).then(contracts => {
                                    this.updateBarrierOffsetBlocks(contracts, false, false, [block.id]);
                                    this.applyBarrierHandlebars('BARRIEROFFSETTYPE_LIST', [ev.blockId], true);
                                    this.updatePredictionBlocks(contracts, [block.id]);
                                    this.updateDurationLists(contracts, false, false, [block.id]);
                                });
                            }
                            block.getChildren().forEach(childBlock => {
                                getNestedTradeOptions(childBlock);
                            });
                        };
                        getNestedTradeOptions(movedBlock);
                    }
                }
            } else if (ev.type === Blockly.Events.CHANGE) {
                // eslint-disable-next-line no-underscore-dangle
                if (this.parentBlock_ !== null) {
                    const symbol = getParentValue(this, 'SYMBOL_LIST');
                    if (!symbol) return;

                    this.pollForContracts(symbol).then(contracts => {
                        if (ev.name === 'SYMBOL_LIST' && ev.oldValue !== ev.newValue) {
                            // Called to update duration options and set min durations
                            this.updateDurationLists(contracts, true, true);
                        } else if (['TRADETYPE_LIST'].includes(ev.name) && ev.oldValue !== ev.newValue) {
                            // Both are called to check if these blocks are required
                            this.updatePredictionBlocks(contracts);
                            this.updateBarrierOffsetBlocks(contracts, true, true);
                            // Handlebars for all tradeOptions barrier-blocks
                            this.applyBarrierHandlebars('BARRIEROFFSETTYPE_LIST', true);
                            // Called to default to smallest durations for symbol
                            this.updateDurationLists(contracts, false, true);
                        } else if (ev.name === 'DURATIONTYPE_LIST' && ev.oldValue !== ev.newValue) {
                            // Called to set barriers based on duration
                            this.updateBarrierOffsetBlocks(contracts, true, true, [ev.blockId]);
                            this.applyBarrierHandlebars('BARRIEROFFSETTYPE_LIST', true, [ev.blockId]);
                            // Called to set min durations for selected unit
                            this.updateDurationLists(contracts, false, true, [ev.blockId]);
                        } else if (
                            ['BARRIEROFFSETTYPE_LIST', 'SECONDBARRIEROFFSETTYPE_LIST'].includes(ev.name) &&
                            ev.oldValue !== ev.newValue
                        ) {
                            // Called to set default values based on barrier type
                            this.updateBarrierOffsetBlocks(contracts, false, true, [ev.blockId]);
                            // Suggests same type barriers
                            this.applyBarrierHandlebars(ev.name, false, [ev.blockId]);
                        }
                        updateInputList(this);
                    });
                }
            }
        },
        pollForContracts(symbol) {
            return new Promise(resolve => {
                const contractsForSymbol = haveContractsForSymbol(symbol);
                if (!contractsForSymbol) {
                    // Register an event and use as a lock to avoid spamming API
                    const event = `contractsLoaded.${symbol}`;
                    if (!globalObserver.isRegistered(event)) {
                        globalObserver.register(event, () => {});
                        getContractsAvailableForSymbol(symbol).then(contracts => {
                            globalObserver.unregisterAll(event); // Release the lock
                            resolve(contracts);
                        });
                    } else {
                        // Request in progress, start polling localStorage until contracts are available.
                        const pollingFn = setInterval(() => {
                            const contracts = haveContractsForSymbol(symbol);
                            if (contracts) {
                                clearInterval(pollingFn);
                                resolve(contracts.available);
                            }
                        }, 100);
                        setTimeout(() => {
                            clearInterval(pollingFn);
                            resolve([]);
                        }, 10000);
                    }
                } else {
                    resolve(contractsForSymbol.available);
                }
            });
        },
        updatePredictionBlocks(contracts, updateOnly = []) {
            getBlocksByType('tradeOptions').forEach(tradeOptionsBlock => {
                if (tradeOptionsBlock.disabled) return;
                if (updateOnly.length && !updateOnly.includes(tradeOptionsBlock.id)) return;

                const predictionInput = tradeOptionsBlock.getInput('PREDICTION');
                if (!predictionInput) return;

                const tradeType = getParentValue(tradeOptionsBlock, 'TRADETYPE_LIST');
                const predictionRange = getPredictionForContracts(contracts, tradeType);

                hideInteractionsFromBlockly(() => {
                    if (!predictionRange.length) {
                        tradeOptionsBlock.removeInput('PREDICTION');
                        return;
                    }
                    predictionInput.setVisible(true);

                    // Attach shadow block with API-returned prediction-value (only if user hasn't defined a value)
                    if (!predictionInput.connection.isConnected()) {
                        predictionInput.attachShadowBlock(predictionRange[0], 'NUM', 'math_number');
                    }
                });
            });
        },
        updateBarrierOffsetBlocks(contracts, useDefaultType = false, setDefaultValue = false, updateOnly = []) {
            getBlocksByType('tradeOptions').forEach(tradeOptionsBlock => {
                if (tradeOptionsBlock.disabled) return;
                if (updateOnly.length && !updateOnly.includes(tradeOptionsBlock.id)) return;

                const tradeType = getParentValue(tradeOptionsBlock, 'TRADETYPE_LIST');
                const selectedDuration = tradeOptionsBlock.getFieldValue('DURATIONTYPE_LIST');
                const selectedBarrierTypes = [
                    tradeOptionsBlock.getFieldValue('BARRIEROFFSETTYPE_LIST'),
                    tradeOptionsBlock.getFieldValue('SECONDBARRIEROFFSETTYPE_LIST'),
                ];
                const barriers = getBarriersForContracts(contracts, tradeType, selectedDuration, selectedBarrierTypes);

                hideInteractionsFromBlockly(() => {
                    const revealBarrierBlock = (barrierValue, inputName) => {
                        const barrierInput = tradeOptionsBlock.getInput(inputName);
                        if (barrierInput) {
                            barrierInput.setVisible(true);
                            if (!barrierInput.connection.isConnected()) {
                                barrierInput.attachShadowBlock(barrierValue, 'NUM', 'math_number');
                            } else if (setDefaultValue) {
                                const connectedBlock = barrierInput.connection.targetBlock();
                                if (connectedBlock.isShadow()) {
                                    connectedBlock.setFieldValue(barrierValue, 'NUM');
                                }
                            }
                        }
                    };

                    const barrierOffsetNames = ['BARRIEROFFSET', 'SECONDBARRIEROFFSET'];
                    const barrierLabels = [translate('High barrier'), translate('Low barrier')];
                    const removeInput = inputName => tradeOptionsBlock.removeInput(inputName);

                    const updateList = (list, options) => {
                        const prevSelectedType = options.find(option => option[1] === list.getValue());
                        list.menuGenerator_ = options; // eslint-disable-line no-underscore-dangle, no-param-reassign
                        if (!useDefaultType && prevSelectedType) {
                            list.setValue('');
                            list.setValue(prevSelectedType[1]);
                            // eslint-disable-next-line no-underscore-dangle
                        } else if (list.menuGenerator_.length) {
                            list.setValue('');
                            list.setValue(list.menuGenerator_[0][1]); // eslint-disable-line no-underscore-dangle
                        }
                    };

                    if (!barriers.values.length) {
                        barrierOffsetNames.forEach(removeInput);
                        return;
                    }

                    barriers.values.forEach((barrierValue, index) => {
                        const typeList = tradeOptionsBlock.getField(`${barrierOffsetNames[index]}TYPE_LIST`);
                        const typeInput = tradeOptionsBlock.getInput(barrierOffsetNames[index]);
                        const absoluteType = [[translate('Absolute'), 'absolute']];

                        if (selectedDuration === 'd') {
                            updateList(typeList, absoluteType);
                        } else if (barriers.allowBothTypes || barriers.allowAbsoluteType) {
                            updateList(typeList, [...config.barrierTypes, ...absoluteType]);
                        } else {
                            updateList(typeList, config.barrierTypes);
                        }

                        if (barriers.values.length === 1) {
                            typeInput.fieldRow[0].setText(`${translate('Barrier')}:`);
                        } else {
                            typeInput.fieldRow[0].setText(`${barrierLabels[index]}:`);
                        }
                        revealBarrierBlock(barrierValue, barrierOffsetNames[index]);
                    });
                    barrierOffsetNames.slice(barriers.values.length).forEach(removeInput);
                });
            });
        },
        updateDurationLists(contracts, useDefaultUnit = false, setMinDuration = false, updateOnly = []) {
            getBlocksByType('tradeOptions').forEach(tradeOptionsBlock => {
                if (tradeOptionsBlock.disabled) return;
                if (updateOnly.length && !updateOnly.includes(tradeOptionsBlock.id)) return;

                const tradeType = getParentValue(tradeOptionsBlock, 'TRADETYPE_LIST');
                const durationTypeList = tradeOptionsBlock.getField('DURATIONTYPE_LIST');
                const selectedDuration = durationTypeList.getValue();

                const durations = getDurationsForContracts(contracts, tradeType);
                const durationOptions = durations.map(duration => [duration.label, duration.unit]);

                hideInteractionsFromBlockly(() => {
                    // Prevent UI flickering by only updating field only if options have changed
                    // eslint-disable-next-line no-underscore-dangle
                    if (JSON.stringify(durationTypeList.menuGenerator_) !== JSON.stringify(durationOptions)) {
                        durationTypeList.menuGenerator_ = durationOptions; // eslint-disable-line no-underscore-dangle
                    }

                    // Set duration to previous selected duration (required for imported strategies)
                    // eslint-disable-next-line no-underscore-dangle
                    const prevSelectedDuration = durationTypeList.menuGenerator_.find(d => d[1] === selectedDuration);
                    if (!useDefaultUnit && prevSelectedDuration) {
                        durationTypeList.setValue('');
                        durationTypeList.setValue(prevSelectedDuration[1]);
                        // eslint-disable-next-line no-underscore-dangle
                    } else if (durationTypeList.menuGenerator_.length) {
                        durationTypeList.setValue('');
                        // eslint-disable-next-line no-underscore-dangle
                        durationTypeList.setValue(durationTypeList.menuGenerator_[0][1]);
                    }

                    // Attach shadow block with min value (only when user hasn't already attached another output block)
                    if (durations.length) {
                        const durationInput = tradeOptionsBlock.getInput('DURATION');
                        if (!durationInput.connection.isConnected()) {
                            durationInput.attachShadowBlock(durations[0].minimum, 'NUM', 'math_number');
                        } else if (setMinDuration) {
                            const connectedBlock = durationInput.connection.targetBlock();
                            const minDuration = durations.find(d => d.unit === selectedDuration);

                            if (connectedBlock.isShadow() && minDuration) {
                                connectedBlock.setFieldValue(minDuration.minimum, 'NUM');
                            }
                        }
                    }
                });
            });
        },
        applyBarrierHandlebars(barrierFieldName, forceDistinct = false, updateOnly = []) {
            getBlocksByType('tradeOptions').forEach(tradeOptionsBlock => {
                if (tradeOptionsBlock.disabled) return;
                if (updateOnly.length && !updateOnly.includes(tradeOptionsBlock.id)) return;

                const newValue = tradeOptionsBlock.getFieldValue(barrierFieldName);
                const otherBarrierListName = () => {
                    if (barrierFieldName === 'BARRIEROFFSETTYPE_LIST') {
                        return 'SECONDBARRIEROFFSETTYPE_LIST';
                    }
                    return 'BARRIEROFFSETTYPE_LIST';
                };

                const otherBarrierList = tradeOptionsBlock.getField(otherBarrierListName());
                if (otherBarrierList) {
                    const otherBarrierType = otherBarrierList.getValue();
                    if (
                        config.barrierTypes.findIndex(t => t[1] === newValue) !== -1 &&
                        (otherBarrierType === 'absolute' || forceDistinct)
                    ) {
                        const otherValue = config.barrierTypes.find(t => t[1] !== newValue);
                        otherBarrierList.setValue(otherValue[1]);
                    } else if (newValue === 'absolute' && otherBarrierType !== 'absolute') {
                        otherBarrierList.setValue('absolute');
                    }
                }
            });
        },
    };
    Blockly.JavaScript.tradeOptions = block => {
        const tradeDefBlock = findTopParentBlock(block);
        if (!tradeDefBlock) {
            return '';
        }

        const durationValue = expectValue(block, 'DURATION');
        const durationType = block.getFieldValue('DURATIONTYPE_LIST');
        const currency = block.getFieldValue('CURRENCY_LIST');
        const amount = expectValue(block, 'AMOUNT');

        const isVisibleField = field => block.getInput(field) && block.getInput(field).isVisible();

        let predictionValue = 'undefined';
        let barrierOffsetValue = 'undefined';
        let secondBarrierOffsetValue = 'undefined';

        if (isVisibleField('PREDICTION')) {
            predictionValue = expectValue(block, 'PREDICTION');
        }
        if (isVisibleField('BARRIEROFFSET')) {
            const barrierOffsetType = block.getFieldValue('BARRIEROFFSETTYPE_LIST');
            const value = expectValue(block, 'BARRIEROFFSET');
            barrierOffsetValue = barrierOffsetType === 'absolute' ? `${value}` : `'${barrierOffsetType}${value}'`;
        }
        if (isVisibleField('SECONDBARRIEROFFSET')) {
            const barrierOffsetType = block.getFieldValue('SECONDBARRIEROFFSETTYPE_LIST');
            const value = expectValue(block, 'SECONDBARRIEROFFSET');
            secondBarrierOffsetValue = barrierOffsetType === 'absolute' ? `${value}` : `'${barrierOffsetType}${value}'`;
        }

        const code = `
            Bot.start({
            limitations: BinaryBotPrivateLimitations,
            duration: ${durationValue},
            duration_unit: '${durationType}',
            currency: '${currency}',
            amount: ${amount},
            prediction: ${predictionValue},
            barrierOffset: ${barrierOffsetValue},
            secondBarrierOffset: ${secondBarrierOffsetValue},
            });
        `;
        return code;
    };
};
<|endoftext|>


<commit_before>var assert = require("assert"),
    _ = require('lodash'),
    mongoose = require('mongoose'),
    TestSchema = new mongoose.Schema({
        testString: {
            type: String,
            default: 'My default value'
        },
        testNumber: Number,
        testArray: [String],
        testBoolean: Boolean,
        testObject: {
            testObjectElement: String
        },
        ignoredField: String
    });
TestSchema.plugin(require('../historical.js'), {ignore: ['ignoredField']});

var connection = mongoose.createConnection('mongodb://localhost/historical_test'),
    TestModel = connection.model('test', TestSchema);

describe('Document', function(){

    var document = new TestModel({
        testNumber: 42,
        testArray: ['test1', 'test2'],
        testObject: {
            testObjectElement: 'test message'
        },
        ignoredField: 'This field is ignored'
    });

    describe('#save()', function(){
        it('An historical record should be created when a document is saved.', function(done){
            document.save(function (e, obj) {
                assert.equal(e, null);
                assert.notEqual(obj, null);

                document = obj;

                obj.historical(function (e, details) {
                    assert.equal(e, null);
                    assert.notEqual(details, null);

                    var diff = _.merge(details.pop().diff, {_id: obj._id, __v: obj.__v});

                    assert.strictEqual(diff.ignoredField, undefined);

                    var withoutIgnored = obj.toObject();
                    delete withoutIgnored['ignoredField'];

                    assert.deepEqual(withoutIgnored, diff);
                    assert.equal(obj.testString, 'My default value');

                    done();
                });
            });
        });

        it('An historical record should be created when a document is modified.', function(done){
            document.testObject.testObjectElement = 'this is a test string';
            document.save(function (e, obj) {
                assert.equal(e, null);
                assert.notEqual(obj, null);

                document = obj;

                obj.historical(function (e, details) {
                    assert.equal(e, null);
                    assert.notEqual(details, null);

                    assert.deepEqual(details.pop().diff, {testObject:{testObjectElement: 'this is a test string'}});

                    done();
                });
            });
        });
    });

    describe('#historicalRestore()', function(){
        it('The document should properly flatten to the appropriate point in time.', function(done){
            var time = new Date(),
                previousValue = document.testObject.testObjectElement;
            document.testObject.testObjectElement = 'this message should be overwritten';
            document.testBoolean = true;

            setTimeout(function(){
                document.save(function (e, obj) {

                    assert.equal(obj.testObject.testObjectElement, 'this message should be overwritten');

                    obj.historicalRestore(time, function(e, obj){
                        assert.equal(obj.testObject.testObjectElement, previousValue);
                        assert.equal(obj.testString, 'My default value');

                        obj.save(function(e, obj){
                            document = obj;

                            done();
                        });
                    });
                });
            }, 10);
        });
    });

    describe('#historicalSnapshot()', function(){
        it('A snapshot should render a copy of the entire document to the historical document', function(done){
            document.historicalSnapshot(function(e, obj){
                obj.historical(function(e, details){

                    var diff = _.merge(details.pop().diff, {_id: obj._id, __v: obj.__v});

                    var withoutIgnored = obj.toObject();
                    delete withoutIgnored['ignoredField'];

                    assert.deepEqual(withoutIgnored, diff);

                    done();

                });
            });
        });
    });

    describe('#remove()', function(){
        it('An historical record should be null when a document is removed.', function(done){
            TestModel.findOne({_id: document.id}, function(e, document){
                assert.equal(e, null);

                document.remove(function(e, obj){
                    assert.equal(e, null);
                    assert.notEqual(obj, null);

                    obj.historical(function(e, details){
                        assert.equal(e, null);
                        assert.notEqual(obj, null);

                        assert.equal(details.pop().diff, null);

                        document.historicalRestore(new Date(), function(e, restored){
                            assert.equal(restored, null);
                            done();
                        });
                    });
                });
            });
        });
    });

    describe('#findOneAndUpdate()', function(){
        var sub_document = new TestModel({
            testNumber: 424,
            testArray: ['test3', 'test4'],
            testObject: {
                testObjectElement: 'something wicked this way comes'
            },
            ignoredField: 'Ignore me'
        });

        it('An historical record should be created when a document is saved.', function(done){
            TestModel.deleteMany({}, function(){
                sub_document.save(function (e, obj) {
                    assert.equal(e, null);
                    assert.notEqual(obj, null);

                    sub_document = obj;

                    obj.historical(function (e, details) {
                        assert.equal(e, null);
                        assert.notEqual(details, null);

                        var diff = _.merge(details.pop().diff, {_id: obj._id, __v: obj.__v});

                        assert.strictEqual(diff.ignoredField, undefined);

                        var withoutIgnored = obj.toObject();
                        delete withoutIgnored['ignoredField'];

                        assert.deepEqual(withoutIgnored, diff);
                        assert.equal(obj.testString, 'My default value');

                        done();
                    });
                });
            });
        });

        it('The post hook for findOneAndUpdate should call historical.js', function(done){

            var update = {
                $set: {
                    testObject: {
                        testObjectElement: 'this is a shift in concsiousness'
                    },
                    testString: 'new test String'
                }
            }

            var query = {
                testNumber: 424,
            }

            TestModel.findOneAndUpdate(query, update, function (e, obj) {
                assert.equal(e, null);
                assert.notEqual(obj, null);

                sub_document = obj;
                done();
            });
        });

        it('An historical record should be created when a document is modified.', function(done){

            var query = {
                testObject: {
                    testObjectElement: 'this is a shift in concsiousness'
                },
                testString: 'new test String'
            }

            var diffTest = {
                testObject: {
                    testObjectElement: 'this is a shift in concsiousness'
                },
                testString: 'new test String'
            }

            setTimeout(function() {
                TestModel.findOne(query, function (e, obj) {
                    assert.equal(e, null);
                    assert.notEqual(obj, null);

                    sub_document = obj;

                    obj.historical(function (e, details) {
                        assert.equal(e, null);
                        assert.notEqual(details, null);

                        assert.deepEqual(details.pop().diff, diffTest);
                        done();
                    });
                });
            }, 100);
        });
    });

    describe('#update()', function(){
        var sub_document = new TestModel({
            testNumber: 424,
            testArray: ['test3', 'test4'],
            testObject: {
                testObjectElement: 'starting test object'
            },
            ignoredField: 'Ignore me'
        });

        it('An historical record should be created when a document is saved.', function(done){
            TestModel.deleteMany({}, function(){
                sub_document.save(function (e, obj) {
                    assert.equal(e, null);
                    assert.notEqual(obj, null);

                    sub_document = obj;

                    obj.historical(function (e, details) {
                        assert.equal(e, null);
                        assert.notEqual(details, null);

                        var diff = _.merge(details.pop().diff, {_id: obj._id, __v: obj.__v});

                        assert.strictEqual(diff.ignoredField, undefined);

                        var withoutIgnored = obj.toObject();
                        delete withoutIgnored['ignoredField'];

                        assert.deepEqual(withoutIgnored, diff);
                        assert.equal(obj.testString, 'My default value');

                        done();
                    });
                });
            });
        });

        it('The post hook for update should call historical.js', function(done){

            var update = {
                $set: {
                    testObject: {
                        testObjectElement: 'new test object'
                    },
                    testString: 'new test string'
                }
            }

            var query = {
                testNumber: 424,
            }

            TestModel.where(query).update(update, function (e, obj) {
                assert.equal(e, null);
                assert.notEqual(obj, null);

                sub_document = obj;
                done();
            });
        });

        it('An historical record should be created when a document is modified.', function(done){

            var query = {
                testObject: {
                    testObjectElement: 'new test object'
                },
                testString: 'new test string'
            }

            var diffTest = {
                testObject: {
                    testObjectElement: 'new test object'
                },
                testString: 'new test string'
            }

            setTimeout(function(){
                TestModel.findOne(query, function (e, obj) {
                    assert.equal(e, null);
                    assert.notEqual(obj, null);

                    sub_document = obj;

                    obj.historical(function (e, details) {
                        assert.equal(e, null);
                        assert.notEqual(details, null);

                        assert.deepEqual(details.pop().diff, diffTest);
                        done();
                    });
                });
            }, 100);
        });
    });

    describe('#findOneAndRemove()', function(){
        var sub_document = new TestModel({
            testNumber: 424,
            testArray: ['test3', 'test4'],
            testObject: {
                testObjectElement: 'starting test object'
            },
            ignoredField: 'Ignore me'
        });

        it('An historical record should be created when a document is saved.', function(done){
            TestModel.deleteMany({}, function(){
                sub_document.save(function (e, obj) {
                    assert.equal(e, null);
                    assert.notEqual(obj, null);

                    sub_document = obj;

                    obj.historical(function (e, details) {
                        assert.equal(e, null);
                        assert.notEqual(details, null);

                        var diff = _.merge(details.pop().diff, {_id: obj._id, __v: obj.__v});

                        assert.strictEqual(diff.ignoredField, undefined);

                        var withoutIgnored = obj.toObject();
                        delete withoutIgnored['ignoredField'];

                        assert.deepEqual(withoutIgnored, diff);
                        assert.equal(obj.testString, 'My default value');

                        done();
                    });
                });
            });
        });

        it('An historical record should be null when a document is removed.', function(done){
            TestModel.findOneAndRemove({_id: sub_document.id}, function(e, obj){

                assert.equal(e, null);
                assert.notEqual(obj, null);

                obj.historical(function(e, details){
                    assert.equal(e, null);
                    assert.notEqual(obj, null);

                    assert.equal(details.pop().diff, null);

                    document.historicalRestore(new Date(), function(e, restored){
                        assert.equal(restored, null);
                        done();
                    });
                });
            });
        });
    });
});
<commit_msg>Remove duplicate test<commit_after>var assert = require("assert"),
    _ = require('lodash'),
    mongoose = require('mongoose'),
    TestSchema = new mongoose.Schema({
        testString: {
            type: String,
            default: 'My default value'
        },
        testNumber: Number,
        testArray: [String],
        testBoolean: Boolean,
        testObject: {
            testObjectElement: String
        },
        ignoredField: String
    });
TestSchema.plugin(require('../historical.js'), {ignore: ['ignoredField']});

var connection = mongoose.createConnection('mongodb://localhost/historical_test'),
    TestModel = connection.model('test', TestSchema);

describe('Document', function(){

    var document = new TestModel({
        testNumber: 42,
        testArray: ['test1', 'test2'],
        testObject: {
            testObjectElement: 'test message'
        },
        ignoredField: 'This field is ignored'
    });

    describe('#save()', function(){
        it('An historical record should be created when a document is saved.', function(done){
            document.save(function (e, obj) {
                assert.equal(e, null);
                assert.notEqual(obj, null);

                document = obj;

                obj.historical(function (e, details) {
                    assert.equal(e, null);
                    assert.notEqual(details, null);

                    var diff = _.merge(details.pop().diff, {_id: obj._id, __v: obj.__v});

                    assert.strictEqual(diff.ignoredField, undefined);

                    var withoutIgnored = obj.toObject();
                    delete withoutIgnored['ignoredField'];

                    assert.deepEqual(withoutIgnored, diff);
                    assert.equal(obj.testString, 'My default value');

                    done();
                });
            });
        });

        it('An historical record should be created when a document is modified.', function(done){
            document.testObject.testObjectElement = 'this is a test string';
            document.save(function (e, obj) {
                assert.equal(e, null);
                assert.notEqual(obj, null);

                document = obj;

                obj.historical(function (e, details) {
                    assert.equal(e, null);
                    assert.notEqual(details, null);

                    assert.deepEqual(details.pop().diff, {testObject:{testObjectElement: 'this is a test string'}});

                    done();
                });
            });
        });
    });

    describe('#historicalRestore()', function(){
        it('The document should properly flatten to the appropriate point in time.', function(done){
            var time = new Date(),
                previousValue = document.testObject.testObjectElement;
            document.testObject.testObjectElement = 'this message should be overwritten';
            document.testBoolean = true;

            setTimeout(function(){
                document.save(function (e, obj) {

                    assert.equal(obj.testObject.testObjectElement, 'this message should be overwritten');

                    obj.historicalRestore(time, function(e, obj){
                        assert.equal(obj.testObject.testObjectElement, previousValue);
                        assert.equal(obj.testString, 'My default value');

                        obj.save(function(e, obj){
                            document = obj;

                            done();
                        });
                    });
                });
            }, 10);
        });
    });

    describe('#historicalSnapshot()', function(){
        it('A snapshot should render a copy of the entire document to the historical document', function(done){
            document.historicalSnapshot(function(e, obj){
                obj.historical(function(e, details){

                    var diff = _.merge(details.pop().diff, {_id: obj._id, __v: obj.__v});

                    var withoutIgnored = obj.toObject();
                    delete withoutIgnored['ignoredField'];

                    assert.deepEqual(withoutIgnored, diff);

                    done();

                });
            });
        });
    });

    describe('#remove()', function(){
        it('An historical record should be null when a document is removed.', function(done){
            TestModel.findOne({_id: document.id}, function(e, document){
                assert.equal(e, null);

                document.remove(function(e, obj){
                    assert.equal(e, null);
                    assert.notEqual(obj, null);

                    obj.historical(function(e, details){
                        assert.equal(e, null);
                        assert.notEqual(obj, null);

                        assert.equal(details.pop().diff, null);

                        document.historicalRestore(new Date(), function(e, restored){
                            assert.equal(restored, null);
                            done();
                        });
                    });
                });
            });
        });
    });

    describe('#findOneAndUpdate()', function(){
        var sub_document = new TestModel({
            testNumber: 424,
            testArray: ['test3', 'test4'],
            testObject: {
                testObjectElement: 'something wicked this way comes'
            },
            ignoredField: 'Ignore me'
        });

        it('The post hook for findOneAndUpdate should call historical.js', function(done){

            var update = {
                $set: {
                    testObject: {
                        testObjectElement: 'this is a shift in concsiousness'
                    },
                    testString: 'new test String'
                }
            }

            var query = {
                testNumber: 424,
            }

            TestModel.findOneAndUpdate(query, update, function (e, obj) {
                assert.equal(e, null);
                assert.notEqual(obj, null);

                sub_document = obj;
                done();
            });
        });

        it('An historical record should be created when a document is modified.', function(done){

            var query = {
                testObject: {
                    testObjectElement: 'this is a shift in concsiousness'
                },
                testString: 'new test String'
            }

            var diffTest = {
                testObject: {
                    testObjectElement: 'this is a shift in concsiousness'
                },
                testString: 'new test String'
            }

            setTimeout(function() {
                TestModel.findOne(query, function (e, obj) {
                    assert.equal(e, null);
                    assert.notEqual(obj, null);

                    sub_document = obj;

                    obj.historical(function (e, details) {
                        assert.equal(e, null);
                        assert.notEqual(details, null);

                        assert.deepEqual(details.pop().diff, diffTest);
                        done();
                    });
                });
            }, 100);
        });
    });

    describe('#update()', function(){
        var sub_document = new TestModel({
            testNumber: 424,
            testArray: ['test3', 'test4'],
            testObject: {
                testObjectElement: 'starting test object'
            },
            ignoredField: 'Ignore me'
        });

        it('An historical record should be created when a document is saved.', function(done){
            TestModel.deleteMany({}, function(){
                sub_document.save(function (e, obj) {
                    assert.equal(e, null);
                    assert.notEqual(obj, null);

                    sub_document = obj;

                    obj.historical(function (e, details) {
                        assert.equal(e, null);
                        assert.notEqual(details, null);

                        var diff = _.merge(details.pop().diff, {_id: obj._id, __v: obj.__v});

                        assert.strictEqual(diff.ignoredField, undefined);

                        var withoutIgnored = obj.toObject();
                        delete withoutIgnored['ignoredField'];

                        assert.deepEqual(withoutIgnored, diff);
                        assert.equal(obj.testString, 'My default value');

                        done();
                    });
                });
            });
        });

        it('The post hook for update should call historical.js', function(done){

            var update = {
                $set: {
                    testObject: {
                        testObjectElement: 'new test object'
                    },
                    testString: 'new test string'
                }
            }

            var query = {
                testNumber: 424,
            }

            TestModel.where(query).update(update, function (e, obj) {
                assert.equal(e, null);
                assert.notEqual(obj, null);

                sub_document = obj;
                done();
            });
        });

        it('An historical record should be created when a document is modified.', function(done){

            var query = {
                testObject: {
                    testObjectElement: 'new test object'
                },
                testString: 'new test string'
            }

            var diffTest = {
                testObject: {
                    testObjectElement: 'new test object'
                },
                testString: 'new test string'
            }

            setTimeout(function(){
                TestModel.findOne(query, function (e, obj) {
                    assert.equal(e, null);
                    assert.notEqual(obj, null);

                    sub_document = obj;

                    obj.historical(function (e, details) {
                        assert.equal(e, null);
                        assert.notEqual(details, null);

                        assert.deepEqual(details.pop().diff, diffTest);
                        done();
                    });
                });
            }, 100);
        });
    });

    describe('#findOneAndRemove()', function(){
        var sub_document = new TestModel({
            testNumber: 424,
            testArray: ['test3', 'test4'],
            testObject: {
                testObjectElement: 'starting test object'
            },
            ignoredField: 'Ignore me'
        });

        it('An historical record should be created when a document is saved.', function(done){
            TestModel.deleteMany({}, function(){
                sub_document.save(function (e, obj) {
                    assert.equal(e, null);
                    assert.notEqual(obj, null);

                    sub_document = obj;

                    obj.historical(function (e, details) {
                        assert.equal(e, null);
                        assert.notEqual(details, null);

                        var diff = _.merge(details.pop().diff, {_id: obj._id, __v: obj.__v});

                        assert.strictEqual(diff.ignoredField, undefined);

                        var withoutIgnored = obj.toObject();
                        delete withoutIgnored['ignoredField'];

                        assert.deepEqual(withoutIgnored, diff);
                        assert.equal(obj.testString, 'My default value');

                        done();
                    });
                });
            });
        });

        it('An historical record should be null when a document is removed.', function(done){
            TestModel.findOneAndRemove({_id: sub_document.id}, function(e, obj){

                assert.equal(e, null);
                assert.notEqual(obj, null);

                obj.historical(function(e, details){
                    assert.equal(e, null);
                    assert.notEqual(obj, null);

                    assert.equal(details.pop().diff, null);

                    document.historicalRestore(new Date(), function(e, restored){
                        assert.equal(restored, null);
                        done();
                    });
                });
            });
        });
    });
});
<|endoftext|>