<commit_before>;(function ( $, window, document, undefined ) {

        // Private Attributes ------------------------------------------

        var pluginName = "modality",
            $body = $('body'),
            defaults = {
                autoBind: true, // automatically bind triggers to modal
                clickOffToClose: true, // click anywhere off of modal to close it
                closeOnEscape: true, // close modal with 'esc' key
                effect: "", // animation style
                innerClass: "mm-wrap", // inner wrapper
                modalClass: "modality-modal", // outer-most container
                onClose: "", // function to run when modal closes
                onOpen: "", // function to run when modal opens
                openClass: "mm-show", // when modal is visible
                openOnLoad: false, // open on page load
                userClass: "" // user can add a class to container
            };

        // Constructor ------------------------------------------------

        /**
         * the modality object
         * @param {object} modal
         * @param {object} options
         * @param {function} callback
         */
        function Modality ( element, options, fn ) {

            var inst = this; // local var for instance

            // Attributes --------------------------------

            inst._defaults = defaults;
            inst._name     = pluginName;
            inst.element   = element;
            inst._$body    = $body;
            inst.id        = $(element).attr( 'id' );
            inst.settings  = $.extend( {}, defaults, options );
            inst.$element  = $(element).wrap(
                '
'+
                    '
'+
                        // user's #modal goes here
                    '
'+
                '
'
            ).show();
            inst.$wrapper   = inst.$element.parents('.' + inst.settings.modalClass);
            inst.$triggers  = $('a[href="#'+inst.id+'"], [data-modality="#'+inst.id+'"]');

            // Events ------------------------------------

            // toggle modal on all triggers
            if( inst.settings.autoBind ) {
                inst.$triggers.each(function() {
                    inst.setTrigger( $(this) );
                });
            }

            // close modal if users clicks anywhere off of it
            if( inst.settings.clickOffToClose ) {
                inst.$wrapper.click( function(e) {
                    e.preventDefault(); if(e.target == inst.$wrapper[0]) inst.close();
                });
            }

            // close modal with 'esc' key
            if( inst.settings.closeOnEscape ) {
                inst._$body.keyup( function (e) {
                    if(e.keyCode == 27) inst.close();
                });
            }

            // Final Touches ------------------------------

            // add node for IE 7 compatibility
            if (navigator.appVersion.indexOf("MSIE 7.") != -1) {
                inst.$wrapper.prepend('

');
            }

            // open modal if set to true
            if( inst.settings.openOnLoad ) inst.open();

            // run the user's callback function
            if( typeof fn == 'function' ) fn();

            // save modal and return it
            return $.modality.lookup[inst.id] = inst;

        }

        // Static Methods ---------------------------------------------

        /**
         * Special plugin objects for plugin and instances.
         * @type {Object}
         * @public
         */
        $[pluginName] = Modality;
        $[pluginName].lookup = [];

        // Class Methods ----------------------------------------------

        $.extend(Modality.prototype, {

            /**
             * opens the modal
             * @param  {function} callback
             * @return {instance}
             */
            open: function ( fn ) {

                // add classes to open the modal
                this.$wrapper.add(this._$body).addClass( this.settings.openClass );

                // run the callback(s)
                if ( typeof this.settings.onOpen == 'function' ) this.settings.onOpen();
                if ( typeof fn == 'function' ) fn();

                return this;
            },

            /**
             * closes the modal
             * @param  {function} callback
             * @return {instance}
             */
            close: function ( fn ) {

                // add classes to open the modal
                this.$wrapper.add(this._$body).removeClass( this.settings.openClass );

                // run the callback(s)
                if ( typeof this.settings.onClose == 'function' ) this.settings.onClose();
                if ( typeof fn == 'function' ) fn();

                return this;
            },

            /**
             * toggles the modal
             * @param  {function} callback
             * @return {instance}
             */
            toggle: function ( fn ) {
                return ( this.isOpen() ) ? this.close( fn ) : this.open( fn );
            },

            /**
             * determines if the modal is open or not
             * @return {Boolean}
             */
            isOpen: function () {
                return this.$wrapper.hasClass(this.settings.openClass);
            },

            /**
             * manually set trigger for a modal
             * @param  {object} - the element you want to be the trigger
             * @return {instance}
             */
            setTrigger: function ( $trigger ) {

                // set local var for instance
                var inst = this;

                // set click event for new trigger
                $trigger.click(function (e) {
                    e.preventDefault(); inst.toggle();
                });

                return inst;
            }

        });

        // jQuery -----------------------------------------------

        /**
         * initalizes modal(s)
         * @param  {object} settings
         * @return {object, array}
         */
        $.fn[ pluginName ] = function ( options ) {
            this.each(function() {
                if ( !$.data( this, "plugin_" + pluginName ) ) {
                    $.data( this, "plugin_" + pluginName, new Modality( this, options ) );
                }
            });

            return this;
        };

})( jQuery, window, document );
<commit_msg>Update modality.jquery.js<commit_after>// -----------------------------------------------------------------
// @author:  Mark McCann (www.markmccann.me)
// @license: None, Public Domain
// -----------------------------------------------------------------

;(function ( $, window, document, undefined ) {

        // Private Attributes ------------------------------------------

        var pluginName = "modality",
            $body = $('body'),
            defaults = {
                autoBind: true, // automatically bind triggers to modal
                clickOffToClose: true, // click anywhere off of modal to close it
                closeOnEscape: true, // close modal with 'esc' key
                effect: "", // animation style
                innerClass: "mm-wrap", // inner wrapper
                modalClass: "modality-modal", // outer-most container
                onClose: "", // function to run when modal closes
                onOpen: "", // function to run when modal opens
                openClass: "mm-show", // when modal is visible
                openOnLoad: false, // open on page load
                userClass: "" // user can add a class to container
            };

        // Constructor ------------------------------------------------

        /**
         * the modality object
         * @param {object} modal
         * @param {object} options
         * @param {function} callback
         */
        function Modality ( element, options, fn ) {

            var inst = this; // local var for instance

            // Attributes --------------------------------

            inst._defaults = defaults;
            inst._name     = pluginName;
            inst.element   = element;
            inst._$body    = $body;
            inst.id        = $(element).attr( 'id' );
            inst.settings  = $.extend( {}, defaults, options );
            inst.$element  = $(element).wrap(
                '
'+
                    '
'+
                        // user's #modal goes here
                    '
'+
                '
'
            ).show();
            inst.$wrapper   = inst.$element.parents('.' + inst.settings.modalClass);
            inst.$triggers  = $('a[href="#'+inst.id+'"], [data-modality="#'+inst.id+'"]');

            // Events ------------------------------------

            // toggle modal on all triggers
            if( inst.settings.autoBind ) {
                inst.$triggers.each(function() {
                    inst.setTrigger( $(this) );
                });
            }

            // close modal if users clicks anywhere off of it
            if( inst.settings.clickOffToClose ) {
                inst.$wrapper.click( function(e) {
                    e.preventDefault(); if(e.target == inst.$wrapper[0]) inst.close();
                });
            }

            // close modal with 'esc' key
            if( inst.settings.closeOnEscape ) {
                inst._$body.keyup( function (e) {
                    if(e.keyCode == 27) inst.close();
                });
            }

            // Final Touches ------------------------------

            // add node for IE 7 compatibility
            if (navigator.appVersion.indexOf("MSIE 7.") != -1) {
                inst.$wrapper.prepend('

');
            }

            // open modal if set to true
            if( inst.settings.openOnLoad ) inst.open();

            // run the user's callback function
            if( typeof fn == 'function' ) fn();

            // save modal and return it
            return $.modality.lookup[inst.id] = inst;

        }

        // Static Methods ---------------------------------------------

        /**
         * Special plugin objects for plugin and instances.
         * @type {Object}
         * @public
         */
        $[pluginName] = Modality;
        $[pluginName].lookup = [];

        // Class Methods ----------------------------------------------

        $.extend(Modality.prototype, {

            /**
             * opens the modal
             * @param  {function} callback
             * @return {instance}
             */
            open: function ( fn ) {

                // add classes to open the modal
                this.$wrapper.add(this._$body).addClass( this.settings.openClass );

                // run the callback(s)
                if ( typeof this.settings.onOpen == 'function' ) this.settings.onOpen();
                if ( typeof fn == 'function' ) fn();

                return this;
            },

            /**
             * closes the modal
             * @param  {function} callback
             * @return {instance}
             */
            close: function ( fn ) {

                // add classes to open the modal
                this.$wrapper.add(this._$body).removeClass( this.settings.openClass );

                // run the callback(s)
                if ( typeof this.settings.onClose == 'function' ) this.settings.onClose();
                if ( typeof fn == 'function' ) fn();

                return this;
            },

            /**
             * toggles the modal
             * @param  {function} callback
             * @return {instance}
             */
            toggle: function ( fn ) {
                return ( this.isOpen() ) ? this.close( fn ) : this.open( fn );
            },

            /**
             * determines if the modal is open or not
             * @return {Boolean}
             */
            isOpen: function () {
                return this.$wrapper.hasClass(this.settings.openClass);
            },

            /**
             * manually set trigger for a modal
             * @param  {object} - the element you want to be the trigger
             * @return {instance}
             */
            setTrigger: function ( $trigger ) {

                // set local var for instance
                var inst = this;

                // set click event for new trigger
                $trigger.click(function (e) {
                    e.preventDefault(); inst.toggle();
                });

                return inst;
            }

        });

        // jQuery -----------------------------------------------

        /**
         * initalizes modal(s)
         * @param  {object} settings
         * @return {object, array}
         */
        $.fn[ pluginName ] = function ( options ) {
            this.each(function() {
                if ( !$.data( this, "plugin_" + pluginName ) ) {
                    $.data( this, "plugin_" + pluginName, new Modality( this, options ) );
                }
            });

            return this;
        };

})( jQuery, window, document );
<|endoftext|>


<commit_before>/**
 * This file is dependent on gulp which automate tasks.
 * Author : # Collins
 */
const gulp = require("gulp"),
  less = require("gulp-less"),
  istanbul = require("gulp-istanbul"),
  livereload = require("gulp-livereload"),
  cleanCSS = require("gulp-clean-css"),
  jasmine = require("gulp-jasmine"),
  jshint = require("gulp-jshint");
const browserSync1 = require("browser-sync").create();
const browserSync2 = require("browser-sync").create();
const browserSync = require("browser-sync").create();
gulp.task("default", ["front","test1"]);
gulp.task("css", () => {
  gulp.src("public/design/css/*.css")
    .pipe(cleanCSS())
    .pipe(gulp.dest("css"))
    .pipe(livereload(".public/index.html"));
});
gulp.task("watch", () => {
  livereload.listen();
  gulp.watch("public/design/css/*.css", ["css"]);
});

// A task that ensures the javascript task is complete before reloading browsers
gulp.task("js-watch", ["js"],  (done) => {
  browserSync.reload();
  done();
});
gulp.task("front",  () => {
  browserSync1.init({
    server : {
      baseDir: "./public"
    },
    port : 3002,
    ui: {
      port:3070
    }
  });
    // Tracks html,js and css changes.I have used to allow reload
  gulp.watch("./public/**/*.{html,js,css}").on("change", browserSync1.reload);
});
gulp.task("test1",  () => {
  browserSync2.init({
    server: {
      baseDir: ["./public/src/", "./jasmine"]},
    port: 3080,
    ui: {
      port: 3090
    }
  });
  gulp.watch("./jasmine/spec/*.js").on("change",browserSync2.reload);
  gulp.watch("./public/src/*.js").on("change", browserSync2.reload);
});

gulp.task("test" ,["pre-test"], () => {
  gulp.src("./jasmine/spec/inverted-index-test.js")
    .pipe(jasmine())
    .pipe(istanbul.writeReports())
    .pipe(istanbul.enforceThresholds({
      thresholds: { global: 90 }
    }));
});

gulp.task("pre-test",  () => {
  return gulp.src(["public/src/**/*.js"])
    // Covering files
    .pipe(istanbul())
    // Write the covered files to a temporary directory
    .pipe(gulp.dest("test-tmp/"))
    // Force `require` to return covered files
    .pipe(istanbul.hookRequire());
});<commit_msg>remove default folder<commit_after>/**
 * This file is dependent on gulp which automate tasks.
 * Author : # Collins
 */
const gulp = require("gulp"),
  less = require("gulp-less"),
  istanbul = require("gulp-istanbul"),
  livereload = require("gulp-livereload"),
  cleanCSS = require("gulp-clean-css"),
  jasmine = require("gulp-jasmine"),
  jshint = require("gulp-jshint");
const browserSync1 = require("browser-sync").create();
const browserSync2 = require("browser-sync").create();
const browserSync = require("browser-sync").create();
gulp.task("default", ["front","test1"]);
gulp.task("css", () => {
  gulp.src("public/design/css/*.css")
    .pipe(cleanCSS())
    .pipe(gulp.dest("css"))
    .pipe(livereload(".public/index.html"));
});
gulp.task("watch", () => {
  livereload.listen();
  gulp.watch("public/design/css/*.css", ["css"]);
});

// A task that ensures the javascript task is complete before reloading browsers
gulp.task("js-watch", ["js"],  (done) => {
  browserSync.reload();
  done();
});
gulp.task("front",  () => {
  browserSync1.init({
    server : {
      baseDir: "./public"
    },
    port : 3002,
    ui: {
      port:3070
    }
  });
    // Tracks html,js and css changes.I have used to allow reload
  gulp.watch("./public/**/*.{html,js,css}").on("change", browserSync1.reload);
});
gulp.task("test1",  () => {
  browserSync2.init({
    server: {
      baseDir: ["./public/src/", "./jasmine"]},
    port: 3080,
    ui: {
      port: 3090
    }
  });
  gulp.watch("./jasmine/spec/*.js").on("change",browserSync2.reload);
  gulp.watch("./public/src/*.js").on("change", browserSync2.reload);
});

gulp.task("test" ,["pre-test"], () => {
  gulp.src("./jasmine/spec/inverted-index-test.js")
    .pipe(jasmine())
    .pipe(istanbul.writeReports())
    .pipe(istanbul.enforceThresholds({
      thresholds: { global: 90 }
    }));
});

gulp.task("pre-test",  () => {
  return gulp.src(["./public/src/inverted-index.js"])
    // Covering files
    .pipe(istanbul())
    // Force `require` to return covered files
    .pipe(istanbul.hookRequire());
});<|endoftext|>


<commit_before>import {toArray} from './utils'

function compareHash(obj){
  if(obj.uuid) return obj.uuid
    //return JSON.stringify(obj)
  //return typeof(obj)+obj.name
}

let jsondiffpatch = require('jsondiffpatch').create({objectHash:compareHash})

export function extractChanges(prev, cur){
    let delta = jsondiffpatch.diff(prev, cur)
    //console.log("delta",delta)
    let result = {added:[],removed:[],changed:[]}

    if(delta && "_t" in delta){

        let removed = []//delta["_0"][0]//delta[0][0]
        let added = []//delta[0][0]//delta[0][1]

        Object.keys(delta).map(function(key){
          //console.log("AAA",key)
          //"_t": "a",	Array delta (member names indicate array indices)

          if(key[0]=="_"){
            if(key!=="_a" && key !=="_t")
            {
              removed.push(delta[key][0])
            }
          }else{
            added.push(delta[key][0])
          }

        })

        result.added = toArray(added).filter(i=>i!==undefined)
        result.removed = toArray(removed).filter(i=>i!==undefined)

        //console.log("added",result.added)
        //console.log("removed",result.removed)
    }else if(prev === undefined){//not handled right in the above case for some reason ??
      result.added = cur
    }

    return result
  }

let instance = require('jsondiffpatch').create({
    objectHash: function(obj, index) {
      if (typeof obj._id !== 'undefined') {
        return obj._id;
      }
      if (typeof obj.id !== 'undefined') {
        return obj.id;
      }
      if (typeof obj.name !== 'undefined') {
        return obj.name;
      }
      return '$$index:' + index;
    }
  })

export function extractChangesBetweenArrays(prev, cur){
  let delta = instance.diff(prev, cur)
  //console.log("delta",delta)
  //console.log("diff",delta)//JSON.stringify(delta, null, 2))

  let result = {added:[],removed:[],changed:[], upserted:[]}

  if(delta && "_t" in delta){

      let removed = []//delta["_0"][0]//delta[0][0]
      let added = []//delta[0][0]//delta[0][1]
      let upserted = []

      if(delta["_t"] ===  "a"){
        //array diff
        //"_t": "a",	Array delta (member names indicate array indices)
        Object.keys(delta).map(function(key){
          if(key !=="_t")
          {
            if(key.length>0 && key.indexOf('_')>-1)
            {
              let realKey = parseInt(key.replace('_',''))
              //console.log("removed",delta, realKey)//,key,delta)
              removed.push(prev[realKey])
            }else
            {
              //added or changed
              //console.log("added or changed",delta, key)
              let realKey = parseInt(key)
              upserted.push(cur[realKey])
            }
          }

        })
      }
      result.added    = toArray(added).filter(i=>i!==undefined)
      result.removed  = toArray(removed).filter(i=>i!==undefined)
      result.upserted = toArray(upserted).filter(i=>i!==undefined)
      //console.log("added",result.added)
      //console.log("removed",result.removed)
  }else if(prev === undefined){//not handled right in the above case for some reason ??
    result.upserted = cur
  }

  return result
}

export function changesFromObservableArrays(data$){
  return data$
    .scan(function(acc, cur){
        return {cur,prev:acc.cur}
      },{prev:undefined,cur:undefined})
    .map(function(typeData){
      let {cur,prev} = typeData
      let changes = extractChangesBetweenArrays(prev,cur)
      return changes
    })
    .share()
}

export function changesFromObservableArrays2(data$){
  data$
    .scan(function(acc, x){
      let cur  = x
      let prev = acc.cur

      cur = Object.keys(cur).map(function(key){
        return cur[key]
      })
      return {cur,prev}
    },{prev:undefined,cur:undefined})
    .map(function(typeData){
      let {cur,prev} = typeData
      let changes = extractChanges(prev,cur)
    return changes
    })
    .share()
}

export function transformEquals(a,b){
    if(!a || !b) return true
    for(let j=0;j<a.length;j++){
      if(a[j]!==b[j]){
        return false
      }
    }
    return true
  }

export function colorsEqual(a,b){
    if(!a || !b) return true
    return a===b
  }

export function entityVisualComparer(prev,cur){
    //console.log("prev",prev,"cur",cur)

    if (!cur)
      return false

    // compare lengths - can save a lot of time
    if (cur.length != prev.length)
      return false

    let sortedCur  = cur.sort()
    let sortedPrev = prev.sort()
    for(var i=0;i<cur.length;i++){
      if(sortedCur[i].typeUid !== sortedPrev[i].typeUid)
        return false

      if(sortedCur[i].id !== sortedPrev[i].id)
        return false

      let curVal = sortedCur[i]
      let preVal = sortedPrev[i]

      /*
        sortedCur[i].color === sortedPrev[i].color
        )*/

      let posEq = transformEquals( curVal.pos, preVal.pos )
      let rotEq = transformEquals( curVal.rot, preVal.rot )
      let scaEq = transformEquals( curVal.sca, preVal.sca )
      let colEq = colorsEqual( curVal.color, preVal.color )
      let allEqual = (posEq && rotEq && scaEq && colEq)
      if(!allEqual) return false
    }

    return true
  }
<commit_msg>fix(diffPatchUtils): added missing return statement<commit_after>import {toArray} from './utils'

function compareHash(obj){
  if(obj.uuid) return obj.uuid
    //return JSON.stringify(obj)
  //return typeof(obj)+obj.name
}

let jsondiffpatch = require('jsondiffpatch').create({objectHash:compareHash})

export function extractChanges(prev, cur){
    let delta = jsondiffpatch.diff(prev, cur)
    //console.log("delta",delta)
    let result = {added:[],removed:[],changed:[]}

    if(delta && "_t" in delta){

        let removed = []//delta["_0"][0]//delta[0][0]
        let added = []//delta[0][0]//delta[0][1]

        Object.keys(delta).map(function(key){
          //console.log("AAA",key)
          //"_t": "a",	Array delta (member names indicate array indices)

          if(key[0]=="_"){
            if(key!=="_a" && key !=="_t")
            {
              removed.push(delta[key][0])
            }
          }else{
            added.push(delta[key][0])
          }

        })

        result.added = toArray(added).filter(i=>i!==undefined)
        result.removed = toArray(removed).filter(i=>i!==undefined)

        //console.log("added",result.added)
        //console.log("removed",result.removed)
    }else if(prev === undefined){//not handled right in the above case for some reason ??
      result.added = cur
    }

    return result
  }

let instance = require('jsondiffpatch').create({
    objectHash: function(obj, index) {
      if (typeof obj._id !== 'undefined') {
        return obj._id;
      }
      if (typeof obj.id !== 'undefined') {
        return obj.id;
      }
      if (typeof obj.name !== 'undefined') {
        return obj.name;
      }
      return '$$index:' + index;
    }
  })

export function extractChangesBetweenArrays(prev, cur){
  let delta = instance.diff(prev, cur)
  //console.log("delta",delta)
  //console.log("diff",delta)//JSON.stringify(delta, null, 2))

  let result = {added:[],removed:[],changed:[], upserted:[]}

  if(delta && "_t" in delta){

      let removed = []//delta["_0"][0]//delta[0][0]
      let added = []//delta[0][0]//delta[0][1]
      let upserted = []

      if(delta["_t"] ===  "a"){
        //array diff
        //"_t": "a",	Array delta (member names indicate array indices)
        Object.keys(delta).map(function(key){
          if(key !=="_t")
          {
            if(key.length>0 && key.indexOf('_')>-1)
            {
              let realKey = parseInt(key.replace('_',''))
              //console.log("removed",delta, realKey)//,key,delta)
              removed.push(prev[realKey])
            }else
            {
              //added or changed
              //console.log("added or changed",delta, key)
              let realKey = parseInt(key)
              upserted.push(cur[realKey])
            }
          }

        })
      }
      result.added    = toArray(added).filter(i=>i!==undefined)
      result.removed  = toArray(removed).filter(i=>i!==undefined)
      result.upserted = toArray(upserted).filter(i=>i!==undefined)
      //console.log("added",result.added)
      //console.log("removed",result.removed)
  }else if(prev === undefined){//not handled right in the above case for some reason ??
    result.upserted = cur
  }

  return result
}

export function changesFromObservableArrays(data$){
  return data$
    .scan(function(acc, cur){
        return {cur,prev:acc.cur}
      },{prev:undefined,cur:undefined})
    .map(function(typeData){
      let {cur,prev} = typeData
      let changes = extractChangesBetweenArrays(prev,cur)
      return changes
    })
    .share()
}

export function changesFromObservableArrays2(data$){
  return data$
    .scan(function(acc, x){
      let cur  = x
      let prev = acc.cur

      cur = Object.keys(cur).map(function(key){
        return cur[key]
      })
      return {cur,prev}
    },{prev:undefined,cur:undefined})
    .map(function(typeData){
      let {cur,prev} = typeData
      let changes = extractChanges(prev,cur)
    return changes
    })
    .share()
}

export function transformEquals(a,b){
    if(!a || !b) return true
    for(let j=0;j<a.length;j++){
      if(a[j]!==b[j]){
        return false
      }
    }
    return true
  }

export function colorsEqual(a,b){
    if(!a || !b) return true
    return a===b
  }

export function entityVisualComparer(prev,cur){
    //console.log("prev",prev,"cur",cur)

    if (!cur)
      return false

    // compare lengths - can save a lot of time
    if (cur.length != prev.length)
      return false

    let sortedCur  = cur.sort()
    let sortedPrev = prev.sort()
    for(var i=0;i<cur.length;i++){
      if(sortedCur[i].typeUid !== sortedPrev[i].typeUid)
        return false

      if(sortedCur[i].id !== sortedPrev[i].id)
        return false

      let curVal = sortedCur[i]
      let preVal = sortedPrev[i]

      /*
        sortedCur[i].color === sortedPrev[i].color
        )*/

      let posEq = transformEquals( curVal.pos, preVal.pos )
      let rotEq = transformEquals( curVal.rot, preVal.rot )
      let scaEq = transformEquals( curVal.sca, preVal.sca )
      let colEq = colorsEqual( curVal.color, preVal.color )
      let allEqual = (posEq && rotEq && scaEq && colEq)
      if(!allEqual) return false
    }

    return true
  }
<|endoftext|>


<commit_before>/* eslint-env mocha */

'use strict';

const debug = require(`debug`)(`test`);
const expect = require(`expect.js`);
const fs = require(`fs`);
const parse5 = require(`parse5-utils`);
const path = require(`path`);
const vdomToHTML = require(`vdom-to-html`);
const snabbdomToHTML = require(`snabbdom-to-html`);

const render = require(`..`);

// expected output from literal imports
const SINGLE_ROOT_IMPORT_HTML = `<div class="test">test</div>`;
const MULTI_ROOT_IMPORT_HTML = `<div>child 1</div><div>child 2</div>`;

function fixtureFilename(name) {
  return path.resolve(__dirname, `fixtures/` + name + `.jade`);
}

function fixture(name) {
  return fs.readFileSync(fixtureFilename(name), `utf8`);
}

function fixtureToHTML(fixtureName, locals, options) {
  options = Object.assign({filename: fixtureFilename(fixtureName)}, options);

  const snabb = options.vdom === `snabbdom`;
  if (snabb && !options.snabbArgs) {
    // putting all 'raw' attrs into the props object allows
    // the same template fixtures to work with both virtual-dom
    // and snabbdom, otherwise snabb expects special keys
    // props: {}, attrs: {}
    options.propsWrapper = `props`;
    options.rawProps = false;
  }

  const compiled = render(fixture(fixtureName), options);
  debug(compiled);

  const fn = eval(`(${compiled})`);
  const root = fn.call({class: `asdf`}, locals);
  const toHTML = snabb ? snabbdomToHTML : vdomToHTML;
  const html = toHTML(root);
  debug(html);

  parse5.parse(html, true);
  return html;
}

describe(`configuration`, function() {
  it(`throws when an invalid vdom config is supplied`, function() {
    expect(render).withArgs(fixture(`simple`), {
      filename: fixtureFilename(`simple`),
      vdom: `foobar`,
    }).to.throwException(`No virtual-jade config found for vdom type: foobar`);
  });

  it(`throws a parser error with filename and line number when rendering a broken template`, function() {
    const file = `break-parser`;
    const filename = fixtureFilename(file);

    expect(render).withArgs(fixture(file), {filename}).to.throwException(function(err) {
      const filenameAndLineNo = new RegExp(filename.replace(`/`, `\/`) + `:2`);
      expect(err.path).to.be(filename);
      expect(err.message).to.match(filenameAndLineNo);
      expect(err.message).to.match(/Unexpected identifier/);
    });
  });

  it(`throws a compiler error with filename and line number when rendering a broken template`, function() {
    const file = `break-compiler`;
    const filename = fixtureFilename(file);
    expect(render).withArgs(fixture(file), {filename}).to.throwException(function(err) {
      const filenameAndLineNo = new RegExp(filename.replace(`/`, `\/`) + `:2`);
      expect(err.path).to.be(filename);
      expect(err.message).to.match(filenameAndLineNo);
      expect(err.message).to.match(/You can only have one top-level tag!/);
    });
  });

  it(`renders a template without options`, function() {
    const compiled = render(fixture(`attributes`));
    expect(compiled).to.contain(`class1`);
    expect(compiled).to.contain(`foo`);
    expect(compiled).to.contain(`doge`);
  });
});

describe(`rendering`, function() {

  // run rendering tests against each supported Virtual DOM library

  const VDOM_LIBS = [
    `virtual-dom`,
    `snabbdom`,
  ];

  for (let vdom of VDOM_LIBS) {
    const renderFixture = function(fixtureName, locals) {
      return fixtureToHTML(fixtureName, locals, {vdom, pretty: true});
    };

    context(`using library "${vdom}"`, function() {
      it(`renders a template`, function() {
        const html = renderFixture(`item`, {
          item: {
            id: `1234`,
            title: `some title`,
            description: `some description`,
            active: true,
          },
        });

        expect(html).to.contain(`item active`);
        expect(html).to.contain(`class="title"`);
        expect(html).to.contain(`<h3`);
        expect(html).to.contain(`</h3>`);
        expect(html).to.contain(`some title`);
        expect(html).to.contain(`some description`);
      });

      it(`beautifies when option is set`, function() {
        const fn = render(fixture(`item`), {pretty: true});
        const lines = fn.split(`\n`);
        expect(lines.length).to.be.greaterThan(15);
        expect(lines[lines.length - 1].trim()).to.be(`}`);
      });

      it(`inserts dynamic tag names`, function() {
        let html = renderFixture(`dynamic-tag`, {myTag: `input`});
        expect(html).to.contain(`<input class="llamas">`);
        html = renderFixture(`dynamic-tag`, {myTag: `textarea`});
        expect(html).to.contain(`<textarea class="llamas">`);
      });

      it(`renders a simple template`, function() {
        const html = renderFixture(`simple`);
        expect(html).to.be(`<div>This is text</div>`);
      });

      it(`translates basic class notation`, function() {
        const html = renderFixture(`class`);
        expect(html).to.contain(`div class="foo"`);
        expect(html).to.contain(`div class="baz llamas"`);
      });

      it(`renders falsey numeric values`, function() {
        const html = renderFixture(`falsey-content`);
        expect(html).to.contain(`<span class="zero-a">0</span>`);
        expect(html).to.contain(`<span class="zero-b">0</span>`);
        expect(html).to.contain(`<span class="zero-c">0</span>`);
        expect(html).to.contain(`<span class="zero-d">0</span>`);
        expect(html).to.contain(`<span class="one">1</span>`);
        expect(html).to.contain(`<span class="null">null</span>`);
      });

      describe(`iteration`, function() {
        it(`runs "each" loops correctly`, function() {
          const html = renderFixture(`each-expression`, {values: [`foo`, `bar`]});
          expect(html).to.contain(`<li>foo</li><li>bar</li>`);
        });

        it(`runs "while" loops correctly`, function() {
          const html = renderFixture(`while`);
          expect(html.match(/<div class=\"item\">/g)).to.have.length(5);
        });

        it(`runs "else" block if no iteration occurred`, function() {
          let html = renderFixture(`each-else`, {items: [`foo`, `bar`]});
          expect(html).to.contain(`<li>foo</li><li>bar</li>`);
          expect(html).not.to.contain(`No items found`);

          html = renderFixture(`each-else`, {items: []});
          expect(html).not.to.contain(`<li>foo</li><li>bar</li>`);
          expect(html).to.contain(`No items found`);
        });

        context(`over object key/value pairs`, function() {
          let html;

          beforeEach(function() {
            html = renderFixture(`each-object`, {obj: {
              llama: `a`,
              wombat: `b`,
            }});
          });

          it(`works with object literals`, function() {
            expect(html).to.contain(`<div class="literal-entry">Literal key: foo / val: bar</div>`);
            expect(html.match(/literal-entry/g)).to.have.length(1);
          });

          it(`works with object variables`, function() {
            expect(html).to.contain(`<div class="obj-entry">Value of llama is a</div>`);
            expect(html).to.contain(`<div class="obj-entry">Value of wombat is b</div>`);
            expect(html.match(/obj-entry/g)).to.have.length(2);
          });

          it(`runs "else" block if no iteration occurred`, function() {
            expect(html).not.to.contain(`entry-present`);
            expect(html).to.contain(`No entries in object`);
          });
        });
      });

      describe(`multiple files`, function() {
        it(`inserts included files`, function() {
          const html = renderFixture(`include`);
          expect(html).to.contain(`<p>Hello</p>`);
          expect(html).to.contain(`<div class="included-content">llamas!!!</div>`);
          expect(html).to.contain(`<p>world</p>`);
        });

        it(`makes included mixins available`, function() {
          const html = renderFixture(`include-with-mixin`);
          expect(html).to.contain(`<div class="foo">`);
          expect(html).to.contain(`<div class="hello">insert me</div>`);
        });

        it(`inserts extended files`, function() {
          const html = renderFixture(`extends`);
          expect(html).to.contain(`<div class="foo">`);
          expect(html).to.contain(`capybara`);
          expect(html).to.contain(`default content`);
        });
      });

      describe(`attributes`, function() {
        it(`adds arbitrary attributes`, function() {
          const html = renderFixture(`attributes`);
          expect(html).to.contain(`required`);
          expect(!html.includes(`something`) || html.includes(`something="false"`)).to.be.ok();
        });

        it(`adds class attributes in array notation`, function() {
          let html = renderFixture(`attributes`);
          expect(html).to.contain(`1 2`);
          html = renderFixture(`attributes`, {variable: `meow`});
          expect(html).to.contain(`1 2 meow`);
        });

        it(`adds class attributes in object notation`, function() {
          let html = renderFixture(`attributes`);
          expect(html).to.contain(`obj1`);
          expect(html).not.to.contain(`obj2`);

          html = renderFixture(`attributes`, {variable: `doge`});
          expect(html).to.contain(`obj1`);
          expect(html).to.contain(`obj2`);
        });

        it(`combines class attributes in different notations gracefully`, function() {
          let html = renderFixture(`attributes`);
          expect(html).to.contain(`mixed`);
          expect(html).to.contain(`mixedArray1`);
          expect(html).not.to.contain(`mixedObj1`);

          html = renderFixture(`attributes`, {var2: `doge`});
          expect(html).to.contain(`mixed`);
          expect(html).to.contain(`mixedArray1`);
          expect(html).to.contain(`mixedObj1`);
          expect(html).to.contain(`doge`);
        });

        it(`renders data attributes`, function() {
          const html = renderFixture(`attributes`, {variable: `capybara`});
          expect(html).to.contain(`data-foo-id="42"`);
          expect(html).to.contain(`data-var="capybara"`);
        });

        it(`converts attributes to correct property names`, function() {
          const html = renderFixture(`attributes`);
          expect(html).to.contain(`autocomplete="on"`);
          expect(html).to.contain(`tabindex="5"`);
          expect(html).to.contain(`for="special-attr"`);
        });
      });

      describe(`case statements`, function() {
        it(`does not execute any cases when none match`, function() {
          const html = renderFixture(`case`);
          expect(html).not.to.contain(`foo`);
          expect(html).not.to.contain(`bar`);
        });

        it(`executes default when no others match`, function() {
          const html = renderFixture(`case`);
          expect(html).to.contain(`llama`);
        });

        it(`executes only one match`, function() {
          const html = renderFixture(`case`, {variable: 1});
          expect(html).to.contain(`span`);
          expect(html).not.to.contain(`input`);
          expect(html).not.to.contain(`textarea`);
        });

        it(`falls through from the first case in a chain`, function() {
          const html = renderFixture(`case`, {variable2: `d`});
          expect(html).to.contain(`bar`);
          expect(html).not.to.contain(`foo`);
        });

        it(`falls through from the middle case in a chain`, function() {
          const html = renderFixture(`case`, {variable2: `e`});
          expect(html).to.contain(`bar`);
          expect(html).not.to.contain(`foo`);
        });

        it(`falls through from the last case in a chain`, function() {
          const html = renderFixture(`case`, {variable2: `f`});
          expect(html).to.contain(`bar`);
          expect(html).not.to.contain(`foo`);
        });
      });

      describe(`code blocks`, function() {
        let html;

        beforeEach(function() {
          html = renderFixture(`code`);
        });

        it(`runs unbuffered code correctly`, function() {
          expect(html).to.contain(`<div class="example bar">`);
        });

        it(`uses locals when evaluating`, function() {
          html = renderFixture(`code`, {x: 0});
          expect(html).to.contain(`<div class="baz">`);

          html = renderFixture(`code`, {x: -1});
          expect(html).not.to.contain(`<div class="baz">`);
        });

        it(`outputs inline buffered code`, function() {
          expect(html).to.contain(`<div class="inline-script"><div class="raw-inline">within another div`);
        });

        it(`outputs standalone buffered code`, function() {
          expect(html).to.contain(`<div class="raw-buffered">raw so raw`);
        });

        it(`does not output unbuffered code`, function() {
          expect(html).not.to.contain(`should not be output`);
        });
      });

      describe(`multiple files`, function() {
        it(`inserts included literal (non-jade) files`, function() {
          const html = renderFixture(`literal-import`);
          expect(html).to.be(
            `<div class="raw">` +
              `<div class="single-root">` +
                `<div>` + SINGLE_ROOT_IMPORT_HTML + `</div>` +
              `</div>` +
              `<div class="multi-root">` +
                `<div>` + MULTI_ROOT_IMPORT_HTML + `</div>` +
              `</div>` +
            `</div>`
          );
        });
      });
    });
  }
});

describe(`snabbdom-specific rendering`, function() {
  const html = fixtureToHTML(`snabb`, {}, {
    vdom: `snabbdom`,
    snabbArgs: true,
    pretty: true,
  });

  it(`renders plain objects`, function() {
    expect(html).to.contain(`<div>Hello</div>`);
  });

  it(`renders arbitrary attributes`, function() {
    expect(html).to.contain(`bar="baz"`);
  });

  it(`renders properties`, function() {
    expect(html).to.contain(`id="xxx"`);
  });

  it(`translates id-selector notation`, function() {
    expect(html).to.contain(`id="id-example1"`);
  });

  it(`combines id-selector notation with props object`, function() {
    expect(html).to.contain(`id="id-example2"`);
    expect(html).to.contain(`href="link2"`);
  });

  it(`combines classes from jade notation and object notation`, function() {
    expect(html).to.match(/class=".+"/);
    const classes = html.match(/class="(.+?)"/)[1].split(` `);
    expect(classes.sort()).to.eql([`jade-class`, `foo`, `obj-class`].sort());
  });

  it(`renders jade-only class notation`, function() {
    expect(html).to.contain(`<div class="single-class"`);
  });

  it(`renders object-only class notation`, function() {
    expect(html).to.contain(`<a class="aclass"`);
  });

  it(`renders literal imports`, function() {
    expect(html).to.contain(MULTI_ROOT_IMPORT_HTML);
  });
});
<commit_msg>newer eslint fixes<commit_after>/* eslint-env mocha */

'use strict';

const debug = require(`debug`)(`test`);
const expect = require(`expect.js`);
const fs = require(`fs`);
const parse5 = require(`parse5-utils`);
const path = require(`path`);
const vdomToHTML = require(`vdom-to-html`);
const snabbdomToHTML = require(`snabbdom-to-html`);

const render = require(`..`);

// expected output from literal imports
const SINGLE_ROOT_IMPORT_HTML = `<div class="test">test</div>`;
const MULTI_ROOT_IMPORT_HTML = `<div>child 1</div><div>child 2</div>`;

function fixtureFilename(name) {
  return path.resolve(__dirname, `fixtures/` + name + `.jade`);
}

function fixture(name) {
  return fs.readFileSync(fixtureFilename(name), `utf8`);
}

function fixtureToHTML(fixtureName, locals, options) {
  options = Object.assign({filename: fixtureFilename(fixtureName)}, options);

  const snabb = options.vdom === `snabbdom`;
  if (snabb && !options.snabbArgs) {
    // putting all 'raw' attrs into the props object allows
    // the same template fixtures to work with both virtual-dom
    // and snabbdom, otherwise snabb expects special keys
    // props: {}, attrs: {}
    options.propsWrapper = `props`;
    options.rawProps = false;
  }

  const compiled = render(fixture(fixtureName), options);
  debug(compiled);

  const fn = eval(`(${compiled})`);
  const root = fn.call({class: `asdf`}, locals);
  const toHTML = snabb ? snabbdomToHTML : vdomToHTML;
  const html = toHTML(root);
  debug(html);

  parse5.parse(html, true);
  return html;
}

describe(`configuration`, function() {
  it(`throws when an invalid vdom config is supplied`, function() {
    expect(render).withArgs(fixture(`simple`), {
      filename: fixtureFilename(`simple`),
      vdom: `foobar`,
    }).to.throwException(`No virtual-jade config found for vdom type: foobar`);
  });

  it(`throws a parser error with filename and line number when rendering a broken template`, function() {
    const file = `break-parser`;
    const filename = fixtureFilename(file);

    expect(render).withArgs(fixture(file), {filename}).to.throwException(function(err) {
      const filenameAndLineNo = new RegExp(filename.replace(`/`, `\\/`) + `:2`);
      expect(err.path).to.be(filename);
      expect(err.message).to.match(filenameAndLineNo);
      expect(err.message).to.match(/Unexpected identifier/);
    });
  });

  it(`throws a compiler error with filename and line number when rendering a broken template`, function() {
    const file = `break-compiler`;
    const filename = fixtureFilename(file);
    expect(render).withArgs(fixture(file), {filename}).to.throwException(function(err) {
      const filenameAndLineNo = new RegExp(filename.replace(`/`, `\\/`) + `:2`);
      expect(err.path).to.be(filename);
      expect(err.message).to.match(filenameAndLineNo);
      expect(err.message).to.match(/You can only have one top-level tag!/);
    });
  });

  it(`renders a template without options`, function() {
    const compiled = render(fixture(`attributes`));
    expect(compiled).to.contain(`class1`);
    expect(compiled).to.contain(`foo`);
    expect(compiled).to.contain(`doge`);
  });
});

describe(`rendering`, function() {

  // run rendering tests against each supported Virtual DOM library

  const VDOM_LIBS = [
    `virtual-dom`,
    `snabbdom`,
  ];

  for (let vdom of VDOM_LIBS) {
    const renderFixture = function(fixtureName, locals) {
      return fixtureToHTML(fixtureName, locals, {vdom, pretty: true});
    };

    context(`using library "${vdom}"`, function() {
      it(`renders a template`, function() {
        const html = renderFixture(`item`, {
          item: {
            id: `1234`,
            title: `some title`,
            description: `some description`,
            active: true,
          },
        });

        expect(html).to.contain(`item active`);
        expect(html).to.contain(`class="title"`);
        expect(html).to.contain(`<h3`);
        expect(html).to.contain(`</h3>`);
        expect(html).to.contain(`some title`);
        expect(html).to.contain(`some description`);
      });

      it(`beautifies when option is set`, function() {
        const fn = render(fixture(`item`), {pretty: true});
        const lines = fn.split(`\n`);
        expect(lines.length).to.be.greaterThan(15);
        expect(lines[lines.length - 1].trim()).to.be(`}`);
      });

      it(`inserts dynamic tag names`, function() {
        let html = renderFixture(`dynamic-tag`, {myTag: `input`});
        expect(html).to.contain(`<input class="llamas">`);
        html = renderFixture(`dynamic-tag`, {myTag: `textarea`});
        expect(html).to.contain(`<textarea class="llamas">`);
      });

      it(`renders a simple template`, function() {
        const html = renderFixture(`simple`);
        expect(html).to.be(`<div>This is text</div>`);
      });

      it(`translates basic class notation`, function() {
        const html = renderFixture(`class`);
        expect(html).to.contain(`div class="foo"`);
        expect(html).to.contain(`div class="baz llamas"`);
      });

      it(`renders falsey numeric values`, function() {
        const html = renderFixture(`falsey-content`);
        expect(html).to.contain(`<span class="zero-a">0</span>`);
        expect(html).to.contain(`<span class="zero-b">0</span>`);
        expect(html).to.contain(`<span class="zero-c">0</span>`);
        expect(html).to.contain(`<span class="zero-d">0</span>`);
        expect(html).to.contain(`<span class="one">1</span>`);
        expect(html).to.contain(`<span class="null">null</span>`);
      });

      describe(`iteration`, function() {
        it(`runs "each" loops correctly`, function() {
          const html = renderFixture(`each-expression`, {values: [`foo`, `bar`]});
          expect(html).to.contain(`<li>foo</li><li>bar</li>`);
        });

        it(`runs "while" loops correctly`, function() {
          const html = renderFixture(`while`);
          expect(html.match(/<div class="item">/g)).to.have.length(5);
        });

        it(`runs "else" block if no iteration occurred`, function() {
          let html = renderFixture(`each-else`, {items: [`foo`, `bar`]});
          expect(html).to.contain(`<li>foo</li><li>bar</li>`);
          expect(html).not.to.contain(`No items found`);

          html = renderFixture(`each-else`, {items: []});
          expect(html).not.to.contain(`<li>foo</li><li>bar</li>`);
          expect(html).to.contain(`No items found`);
        });

        context(`over object key/value pairs`, function() {
          let html;

          beforeEach(function() {
            html = renderFixture(`each-object`, {obj: {
              llama: `a`,
              wombat: `b`,
            }});
          });

          it(`works with object literals`, function() {
            expect(html).to.contain(`<div class="literal-entry">Literal key: foo / val: bar</div>`);
            expect(html.match(/literal-entry/g)).to.have.length(1);
          });

          it(`works with object variables`, function() {
            expect(html).to.contain(`<div class="obj-entry">Value of llama is a</div>`);
            expect(html).to.contain(`<div class="obj-entry">Value of wombat is b</div>`);
            expect(html.match(/obj-entry/g)).to.have.length(2);
          });

          it(`runs "else" block if no iteration occurred`, function() {
            expect(html).not.to.contain(`entry-present`);
            expect(html).to.contain(`No entries in object`);
          });
        });
      });

      describe(`multiple files`, function() {
        it(`inserts included files`, function() {
          const html = renderFixture(`include`);
          expect(html).to.contain(`<p>Hello</p>`);
          expect(html).to.contain(`<div class="included-content">llamas!!!</div>`);
          expect(html).to.contain(`<p>world</p>`);
        });

        it(`makes included mixins available`, function() {
          const html = renderFixture(`include-with-mixin`);
          expect(html).to.contain(`<div class="foo">`);
          expect(html).to.contain(`<div class="hello">insert me</div>`);
        });

        it(`inserts extended files`, function() {
          const html = renderFixture(`extends`);
          expect(html).to.contain(`<div class="foo">`);
          expect(html).to.contain(`capybara`);
          expect(html).to.contain(`default content`);
        });
      });

      describe(`attributes`, function() {
        it(`adds arbitrary attributes`, function() {
          const html = renderFixture(`attributes`);
          expect(html).to.contain(`required`);
          expect(!html.includes(`something`) || html.includes(`something="false"`)).to.be.ok();
        });

        it(`adds class attributes in array notation`, function() {
          let html = renderFixture(`attributes`);
          expect(html).to.contain(`1 2`);
          html = renderFixture(`attributes`, {variable: `meow`});
          expect(html).to.contain(`1 2 meow`);
        });

        it(`adds class attributes in object notation`, function() {
          let html = renderFixture(`attributes`);
          expect(html).to.contain(`obj1`);
          expect(html).not.to.contain(`obj2`);

          html = renderFixture(`attributes`, {variable: `doge`});
          expect(html).to.contain(`obj1`);
          expect(html).to.contain(`obj2`);
        });

        it(`combines class attributes in different notations gracefully`, function() {
          let html = renderFixture(`attributes`);
          expect(html).to.contain(`mixed`);
          expect(html).to.contain(`mixedArray1`);
          expect(html).not.to.contain(`mixedObj1`);

          html = renderFixture(`attributes`, {var2: `doge`});
          expect(html).to.contain(`mixed`);
          expect(html).to.contain(`mixedArray1`);
          expect(html).to.contain(`mixedObj1`);
          expect(html).to.contain(`doge`);
        });

        it(`renders data attributes`, function() {
          const html = renderFixture(`attributes`, {variable: `capybara`});
          expect(html).to.contain(`data-foo-id="42"`);
          expect(html).to.contain(`data-var="capybara"`);
        });

        it(`converts attributes to correct property names`, function() {
          const html = renderFixture(`attributes`);
          expect(html).to.contain(`autocomplete="on"`);
          expect(html).to.contain(`tabindex="5"`);
          expect(html).to.contain(`for="special-attr"`);
        });
      });

      describe(`case statements`, function() {
        it(`does not execute any cases when none match`, function() {
          const html = renderFixture(`case`);
          expect(html).not.to.contain(`foo`);
          expect(html).not.to.contain(`bar`);
        });

        it(`executes default when no others match`, function() {
          const html = renderFixture(`case`);
          expect(html).to.contain(`llama`);
        });

        it(`executes only one match`, function() {
          const html = renderFixture(`case`, {variable: 1});
          expect(html).to.contain(`span`);
          expect(html).not.to.contain(`input`);
          expect(html).not.to.contain(`textarea`);
        });

        it(`falls through from the first case in a chain`, function() {
          const html = renderFixture(`case`, {variable2: `d`});
          expect(html).to.contain(`bar`);
          expect(html).not.to.contain(`foo`);
        });

        it(`falls through from the middle case in a chain`, function() {
          const html = renderFixture(`case`, {variable2: `e`});
          expect(html).to.contain(`bar`);
          expect(html).not.to.contain(`foo`);
        });

        it(`falls through from the last case in a chain`, function() {
          const html = renderFixture(`case`, {variable2: `f`});
          expect(html).to.contain(`bar`);
          expect(html).not.to.contain(`foo`);
        });
      });

      describe(`code blocks`, function() {
        let html;

        beforeEach(function() {
          html = renderFixture(`code`);
        });

        it(`runs unbuffered code correctly`, function() {
          expect(html).to.contain(`<div class="example bar">`);
        });

        it(`uses locals when evaluating`, function() {
          html = renderFixture(`code`, {x: 0});
          expect(html).to.contain(`<div class="baz">`);

          html = renderFixture(`code`, {x: -1});
          expect(html).not.to.contain(`<div class="baz">`);
        });

        it(`outputs inline buffered code`, function() {
          expect(html).to.contain(`<div class="inline-script"><div class="raw-inline">within another div`);
        });

        it(`outputs standalone buffered code`, function() {
          expect(html).to.contain(`<div class="raw-buffered">raw so raw`);
        });

        it(`does not output unbuffered code`, function() {
          expect(html).not.to.contain(`should not be output`);
        });
      });

      describe(`multiple files`, function() {
        it(`inserts included literal (non-jade) files`, function() {
          const html = renderFixture(`literal-import`);
          expect(html).to.be(
            `<div class="raw">` +
              `<div class="single-root">` +
                `<div>` + SINGLE_ROOT_IMPORT_HTML + `</div>` +
              `</div>` +
              `<div class="multi-root">` +
                `<div>` + MULTI_ROOT_IMPORT_HTML + `</div>` +
              `</div>` +
            `</div>`
          );
        });
      });
    });
  }
});

describe(`snabbdom-specific rendering`, function() {
  const html = fixtureToHTML(`snabb`, {}, {
    vdom: `snabbdom`,
    snabbArgs: true,
    pretty: true,
  });

  it(`renders plain objects`, function() {
    expect(html).to.contain(`<div>Hello</div>`);
  });

  it(`renders arbitrary attributes`, function() {
    expect(html).to.contain(`bar="baz"`);
  });

  it(`renders properties`, function() {
    expect(html).to.contain(`id="xxx"`);
  });

  it(`translates id-selector notation`, function() {
    expect(html).to.contain(`id="id-example1"`);
  });

  it(`combines id-selector notation with props object`, function() {
    expect(html).to.contain(`id="id-example2"`);
    expect(html).to.contain(`href="link2"`);
  });

  it(`combines classes from jade notation and object notation`, function() {
    expect(html).to.match(/class=".+"/);
    const classes = html.match(/class="(.+?)"/)[1].split(` `);
    expect(classes.sort()).to.eql([`jade-class`, `foo`, `obj-class`].sort());
  });

  it(`renders jade-only class notation`, function() {
    expect(html).to.contain(`<div class="single-class"`);
  });

  it(`renders object-only class notation`, function() {
    expect(html).to.contain(`<a class="aclass"`);
  });

  it(`renders literal imports`, function() {
    expect(html).to.contain(MULTI_ROOT_IMPORT_HTML);
  });
});
<|endoftext|>


<commit_before>'use strict';

var Typeahead = require('suggestions');
var debounce = require('lodash.debounce');
var extend = require('xtend');
var EventEmitter = require('events').EventEmitter;
var exceptions = require('./exceptions');
var MapboxClient = require('mapbox/lib/services/geocoding');

// Mapbox Geocoder version
var API = 'https://api.mapbox.com/geocoding/v5/mapbox.places/';

/**
 * A geocoder component using Mapbox Geocoding APi
 * @class MapboxGeocoder
 *
 * @param {Object} options
 * @param {String} [options.accessToken=null] Required unless `mapboxgl.accessToken` is set globally
 * @param {Array<number>} options.proximity If set, search results closer to these coordinates will be given higher priority.
 * @param {Array<number>} options.bbox Limit results to a given bounding box provided as `[minX, minY, maxX, maxY]`.
 * @param {Number} [options.zoom=16] On geocoded result what zoom level should the map animate to.
 * @param {Boolean} [options.flyTo=true] If false, animating the map to a selected result is disabled.
 * @param {String} [options.placeholder="Search"] Override the default placeholder attribute value.
 * @param {string} options.types a comma seperated list of types that filter results to match those specified. See https://www.mapbox.com/developers/api/geocoding/#filter-type for available types.
 * @param {string} options.country a comma seperated list of country codes to limit results to specified country or countries.
 * @example
 * var geocoder = new MapboxGeocoder();
 * map.addControl(geocoder);
 * @return {MapboxGeocoder} `this`
 */
function MapboxGeocoder(options) {
  this._ev = new EventEmitter();
  this.options = extend({}, this.options, options);
}

MapboxGeocoder.prototype = {

  options: {
    placeholder: 'Search',
    zoom: 16,
    flyTo: true
  },

  onAdd: function(map) {
    this.mapboxClient = new MapboxClient(this.options.accessToken);

    // Template
    var el = document.createElement('div');
    el.className = 'mapboxgl-ctrl-geocoder';

    var icon = document.createElement('span');
    icon.className = 'geocoder-icon geocoder-icon-search';

    this._inputEl = document.createElement('input');
    this._inputEl.type = 'text';
    this._inputEl.placeholder = this.options.placeholder;

    this._inputEl.addEventListener('keydown', debounce(function(e) {
      if (!e.target.value) return this._clearEl.classList.remove('active');

      // TAB, ESC, LEFT, RIGHT, ENTER, UP, DOWN
      if (e.metaKey || [9, 27, 37, 39, 13, 38, 40].indexOf(e.keyCode) !== -1) return;
      this._queryFromInput(e.target.value);
    }.bind(this), 200));

    this._inputEl.addEventListener('change', function(e) {
      if (e.target.value) this._clearEl.classList.add('active');

      var selected = this._typeahead.selected;
      if (selected) {
        if (this.options.flyTo) {
          if (!exceptions[selected.id] &&
              (selected.bbox && selected.context && selected.context.length <= 3 ||
              selected.bbox && !selected.context)) {
            var bbox = selected.bbox;
            map.fitBounds([[bbox[0], bbox[1]],[bbox[2], bbox[3]]]);
          } else if (exceptions[selected.id]) {
            // Certain geocoder search results return (and therefore zoom to fit)
            // an unexpectedly large bounding box: for example, both Russia and the
            // USA span both sides of -180/180, or France includes the island of
            // Reunion in the Indian Ocean. An incomplete list of these exceptions
            // at ./exceptions.json provides "reasonable" bounding boxes as a
            // short-term solution; this may be amended as necessary.
            map.fitBounds(exceptions[selected.id].bbox);
          } else {
            map.flyTo({
              center: selected.center,
              zoom: this.options.zoom
            });
          }
        }
        this._input = selected;
        this._ev.emit('result', { result: selected });
      }
    }.bind(this));

    var actions = document.createElement('div');
    actions.classList.add('geocoder-pin-right');

    this._clearEl = document.createElement('button');
    this._clearEl.className = 'geocoder-icon geocoder-icon-close';
    this._clearEl.addEventListener('click', this._clear.bind(this));

    this._loadingEl = document.createElement('span');
    this._loadingEl.className = 'geocoder-icon geocoder-icon-loading';

    actions.appendChild(this._clearEl);
    actions.appendChild(this._loadingEl);

    el.appendChild(icon);
    el.appendChild(this._inputEl);
    el.appendChild(actions);

    this._typeahead = new Typeahead(this._inputEl, [], { filter: false });
    this._typeahead.getItemValue = function(item) { return item.place_name; };

    return el;
  },

  _geocode: function(q, callback) {
    this._loadingEl.classList.add('active');
    this._ev.emit('loading');

    var options = [];
    if (this.options.proximity) options.push('proximity=' + this.options.proximity.join());
    if (this.options.bbox) options.push('bbox=' + this.options.bbox.join());
    if (this.options.country) options.push('country=' + this.options.country);
    if (this.options.types) options.push('types=' + this.options.types);

    var accessToken = this.options.accessToken;

    this.request.abort();
    this.request.open('GET', API + encodeURIComponent(q.trim()) + '.json?' + options.join('&'), true);
    this.request.onload = function() {
      this._loadingEl.classList.remove('active');
      if (this.request.status >= 200 && this.request.status < 400) {
        var data = JSON.parse(this.request.responseText);
        if (data.features.length) {
          this._clearEl.classList.add('active');
        } else {
          this._clearEl.classList.remove('active');
          this._typeahead.selected = null;
        }

        this._ev.emit('results', { results: data.features });
        this._typeahead.update(data.features);
        return callback(data.features);
      } else {
        this._ev.emit('error', { error: JSON.parse(this.request.responseText).message });
      }
    }.bind(this);

    this.request.onerror = function() {
      this._loadingEl.classList.remove('active');
      this._ev.emit('error', { error: JSON.parse(this.request.responseText).message });
    }.bind(this);

    this.request.send();
  },

  _queryFromInput: function(q) {
    q = q.trim();
    if (!q) this._clear();
    if (q.length > 2) {
      this._geocode(q, function(results) {
        this._results = results;
      }.bind(this));
    }
  },

  _change: function() {
    var onChange = document.createEvent('HTMLEvents');
    onChange.initEvent('change', true, false);
    this._inputEl.dispatchEvent(onChange);
  },

  _query: function(input) {
    if (!input) return;
    this._geocode(input, function(results) {
      if (!results.length) return;
      var result = results[0];
      this._results = results;
      this._typeahead.selected = result;
      this._inputEl.value = result.place_name;
      this._change();
    }.bind(this));
  },

  _setInput: function(input) {
    if (!input) return;
    // Set input value to passed value and clear everything else.
    this._inputEl.value = input;
    this._input = null;
    this._typeahead.selected = null;
    this._typeahead.clear();
    this._change();
  },

  _clear: function() {
    this._input = null;
    this._inputEl.value = '';
    this._typeahead.selected = null;
    this._typeahead.clear();
    this._change();
    this._inputEl.focus();
    this._clearEl.classList.remove('active');
    this._ev.emit('clear');
  },

  /**
   * Return the input
   * @returns {Object} input
   */
  getResult: function() {
    return this._input;
  },

  /**
   * Set & query the input
   * @param {string} query location name or other search input
   * @returns {Geocoder} this
   */
  query: function(query) {
    this._query(query);
    return this;
  },

  /**
   * Set input
   * @param {string} query location name or other search input
   * @returns {Geocoder} this
   */
  setInput: function(value) {
    this._setInput(value);
    return this;
  },

  /**
   * Subscribe to events that happen within the plugin.
   * @param {String} type name of event. Available events and the data passed into their respective event objects are:
   *
   * - __clear__ `Emitted when the input is cleared`
   * - __loading__ `Emitted when the geocoder is looking up a query`
   * - __results__ `{ results } Fired when the geocoder returns a response`
   * - __result__ `{ result } Fired when input is set`
   * - __error__ `{ error } Error as string
   * @param {Function} fn function that's called when the event is emitted.
   * @returns {Geocoder} this;
   */
  on: function(type, fn) {
    this._ev.on(type, fn);
    return this;
  },

  /**
   * Remove an event
   * @returns {Geocoder} this
   * @param {String} type Event name.
   * @param {Function} fn Function that should unsubscribe to the event emitted.
   */
  off: function(type, fn) {
    this._ev.removeListener(type, fn);
    return this;
  }
};

module.exports = MapboxGeocoder;
<commit_msg>Remove needless indirection with query and setInput. Fixes #75<commit_after>'use strict';

var Typeahead = require('suggestions');
var debounce = require('lodash.debounce');
var extend = require('xtend');
var EventEmitter = require('events').EventEmitter;
var exceptions = require('./exceptions');
var MapboxClient = require('mapbox/lib/services/geocoding');

/**
 * A geocoder component using Mapbox Geocoding APi
 * @class MapboxGeocoder
 *
 * @param {Object} options
 * @param {String} [options.accessToken=null] Required unless `mapboxgl.accessToken` is set globally
 * @param {Array<number>} options.proximity If set, search results closer to these coordinates will be given higher priority.
 * @param {Array<number>} options.bbox Limit results to a given bounding box provided as `[minX, minY, maxX, maxY]`.
 * @param {Number} [options.zoom=16] On geocoded result what zoom level should the map animate to.
 * @param {Boolean} [options.flyTo=true] If false, animating the map to a selected result is disabled.
 * @param {String} [options.placeholder="Search"] Override the default placeholder attribute value.
 * @param {string} options.types a comma seperated list of types that filter results to match those specified. See https://www.mapbox.com/developers/api/geocoding/#filter-type for available types.
 * @param {string} options.country a comma seperated list of country codes to limit results to specified country or countries.
 * @example
 * var geocoder = new MapboxGeocoder();
 * map.addControl(geocoder);
 * @return {MapboxGeocoder} `this`
 */
function MapboxGeocoder(options) {
  this._ev = new EventEmitter();
  this.options = extend({}, this.options, options);
}

MapboxGeocoder.prototype = {

  options: {
    placeholder: 'Search',
    zoom: 16,
    flyTo: true
  },

  onAdd: function(map) {
    this.mapboxClient = new MapboxClient(this.options.accessToken);

    // Template
    var el = document.createElement('div');
    el.className = 'mapboxgl-ctrl-geocoder';

    var icon = document.createElement('span');
    icon.className = 'geocoder-icon geocoder-icon-search';

    this._inputEl = document.createElement('input');
    this._inputEl.type = 'text';
    this._inputEl.placeholder = this.options.placeholder;

    this._inputEl.addEventListener('keydown', debounce(function(e) {
      if (!e.target.value) return this._clearEl.classList.remove('active');

      // TAB, ESC, LEFT, RIGHT, ENTER, UP, DOWN
      if (e.metaKey || [9, 27, 37, 39, 13, 38, 40].indexOf(e.keyCode) !== -1) return;
      this._queryFromInput(e.target.value);
    }.bind(this), 200));

    this._inputEl.addEventListener('change', function(e) {
      if (e.target.value) this._clearEl.classList.add('active');

      var selected = this._typeahead.selected;
      if (selected) {
        if (this.options.flyTo) {
          if (!exceptions[selected.id] &&
              (selected.bbox && selected.context && selected.context.length <= 3 ||
              selected.bbox && !selected.context)) {
            var bbox = selected.bbox;
            map.fitBounds([[bbox[0], bbox[1]],[bbox[2], bbox[3]]]);
          } else if (exceptions[selected.id]) {
            // Certain geocoder search results return (and therefore zoom to fit)
            // an unexpectedly large bounding box: for example, both Russia and the
            // USA span both sides of -180/180, or France includes the island of
            // Reunion in the Indian Ocean. An incomplete list of these exceptions
            // at ./exceptions.json provides "reasonable" bounding boxes as a
            // short-term solution; this may be amended as necessary.
            map.fitBounds(exceptions[selected.id].bbox);
          } else {
            map.flyTo({
              center: selected.center,
              zoom: this.options.zoom
            });
          }
        }
        this._input = selected;
        this._ev.emit('result', { result: selected });
      }
    }.bind(this));

    var actions = document.createElement('div');
    actions.classList.add('geocoder-pin-right');

    this._clearEl = document.createElement('button');
    this._clearEl.className = 'geocoder-icon geocoder-icon-close';
    this._clearEl.addEventListener('click', this._clear.bind(this));

    this._loadingEl = document.createElement('span');
    this._loadingEl.className = 'geocoder-icon geocoder-icon-loading';

    actions.appendChild(this._clearEl);
    actions.appendChild(this._loadingEl);

    el.appendChild(icon);
    el.appendChild(this._inputEl);
    el.appendChild(actions);

    this._typeahead = new Typeahead(this._inputEl, [], { filter: false });
    this._typeahead.getItemValue = function(item) { return item.place_name; };

    return el;
  },

  _geocode: function(q, callback) {
    this._loadingEl.classList.add('active');
    this._ev.emit('loading');
    this.mapboxClient.geocodeForward(q, this.options, function(err, res) {
      this._loadingEl.classList.remove('active');

      if (err) {
        this._ev.emit('error', { error: err });
        return;
      }

      if (res.features.length) {
        this._clearEl.classList.add('active');
      } else {
        this._clearEl.classList.remove('active');
        this._typeahead.selected = null;
      }

      this._ev.emit('results', res);
      this._typeahead.update(res.features);
      return callback(res.features);

    }.bind(this));
  },

  _queryFromInput: function(q) {
    q = q.trim();
    if (!q) this._clear();
    if (q.length > 2) {
      this._geocode(q, function(results) {
        this._results = results;
      }.bind(this));
    }
  },

  _change: function() {
    var onChange = document.createEvent('HTMLEvents');
    onChange.initEvent('change', true, false);
    this._inputEl.dispatchEvent(onChange);
  },

  /**
   * Set & query the input
   * @param {string} query location name or other search input
   * @returns {Geocoder} this
   */
  query: function(input) {
    if (!input) return;
    this._geocode(input, function(results) {
      if (!results.length) return;
      var result = results[0];
      this._results = results;
      this._typeahead.selected = result;
      this._inputEl.value = result.place_name;
      this._change();
    }.bind(this));
    return this;
  },

  /**
   * Set input
   * @param {string} query location name or other search input
   * @returns {Geocoder} this
   */
  setInput: function(input) {
    if (!input) return;
    // Set input value to passed value and clear everything else.
    this._inputEl.value = input;
    this._input = null;
    this._typeahead.selected = null;
    this._typeahead.clear();
    this._change();
    return this;
  },

  _clear: function() {
    this._input = null;
    this._inputEl.value = '';
    this._typeahead.selected = null;
    this._typeahead.clear();
    this._change();
    this._inputEl.focus();
    this._clearEl.classList.remove('active');
    this._ev.emit('clear');
  },

  /**
   * Return the input
   * @returns {Object} input
   */
  getResult: function() {
    return this._input;
  },

  /**
   * Subscribe to events that happen within the plugin.
   * @param {String} type name of event. Available events and the data passed into their respective event objects are:
   *
   * - __clear__ `Emitted when the input is cleared`
   * - __loading__ `Emitted when the geocoder is looking up a query`
   * - __results__ `{ results } Fired when the geocoder returns a response`
   * - __result__ `{ result } Fired when input is set`
   * - __error__ `{ error } Error as string
   * @param {Function} fn function that's called when the event is emitted.
   * @returns {Geocoder} this;
   */
  on: function(type, fn) {
    this._ev.on(type, fn);
    return this;
  },

  /**
   * Remove an event
   * @returns {Geocoder} this
   * @param {String} type Event name.
   * @param {Function} fn Function that should unsubscribe to the event emitted.
   */
  off: function(type, fn) {
    this._ev.removeListener(type, fn);
    return this;
  }
};

module.exports = MapboxGeocoder;
<|endoftext|>


<commit_before>import gulp from 'gulp'
import git from 'gulp-git'
import bump from 'gulp-bump'
import tagVersion from 'gulp-tag-version'

;['major', 'minor', 'patch'].forEach((type) => {
  gulp.task(`bump:${type}`, () =>
    gulp.src('./package.json')
    .pipe(bump({ type }))
    .pipe(gulp.dest('./'))
    .pipe(git.commit('bumps version'))
    .pipe(tagVersion())
  )
})
gulp.task('bump', ['bump:patch'])
<commit_msg>Add build script to gulp script<commit_after>import gulp from 'gulp'
import git from 'gulp-git'
import bump from 'gulp-bump'
import tagVersion from 'gulp-tag-version'
import babel from 'gulp-babel'

;['major', 'minor', 'patch'].forEach((type) => {
  gulp.task(`bump:${type}`, ['build'], () =>
    gulp.src('./package.json')
    .pipe(bump({ type }))
    .pipe(gulp.dest('./'))
    .pipe(git.commit('bumps version'))
    .pipe(tagVersion())
  )
})
gulp.task('bump', ['bump:patch'])

gulp.task('build', ['build:babel'])
gulp.task('build:babel', () =>
  gulp.src('src/*.js')
		.pipe(babel())
		.pipe(gulp.dest('lib'))
)
<|endoftext|>


<commit_before>var assert = require("assert")
var request = require("supertest")
var app = require('../app');
  describe('calling root url', function(){
    it('should return 500', function(done){
     request(app)
        .get('/')
        .expect(500,done);
    })
  })

<commit_msg>fixed test<commit_after>var assert = require("assert")
var request = require("supertest")
var app = require('../app');
  describe('calling root url', function(){
    it('should return 200', function(done){
     request(app)
        .get('/')
        .expect(200,done);
    })
  })

<|endoftext|>


<commit_before>(function()
{
    "use strict";

    var ns = namespace( "pc.model" );

    /**
     * A testdata set.

     * Testdata represents a set of pictures and statuses used to play
     *
     * @namespace pc.model
     * @class TestData
     * @extends Backbone.Model
     */
    ns.TestData = Backbone.Model.extend( {

        MAX_INIT_ITEMS: 12,
        TEST_ROUNDS:    20,
        INITIAL_SCORE:  1000,
        TEST_DATA_SIZE: 5,

        /**
         * Create a new set of testdata
         *
         * @method
         * @constructor
         */
        initialize: function()
        {
            this.player = pc.model.FacebookPlayer.getInstance();
            this.set( 'pictures', new pc.model.FacebookPictureCollection() );
            this.set( 'statuses', new pc.model.FacebookStatusCollection() );
            this.set( 'data', undefined );

            this.rateTupples = {};
            this.alreadyComparedTuples = [];
            this.ratePtr = 0;
            this.elo = new EloRating(); // NOQA
            this.remainingRates = this.TEST_ROUNDS;
            this.rateSource = [];
        },

        /**
         * Set the scope for the game.

         * Can be set to use images or statuses only or a combination of both
         *
         * @method setScope
         * @param {pc.model.TestData.Scope} scope The scope to use
         * @throws "E_UNKNOWN_SCOPE" If the scope is unknown
         */
        setScope: function( scope )
        {
            switch ( scope ) {
                case pc.model.TestData.Scope.IMAGES:
                    this._collectPictures();
                    break;
                case pc.model.TestData.Scope.STATUSES:
                    this._collectStatuses();
                    break;
                case pc.model.TestData.Scope.BOTH:
                    this._collectPictures();
                    this._collectStatuses();
                    break;
                default:
                    console.error( "[TestData] Unknown game scope:", scope );
                    throw "E_UNKNOWN_SCOPE";
            }

            console.debug( "[TestData] Collected data", this.rateSource, "with scope", scope );

            var pointer = this.MAX_INIT_ITEMS;
            this.rateSource = _.union( this.get( 'pictures' ).models, this.get( 'statuses' ).models );
            this.rateSource = _.shuffle( this.rateSource );
            console.debug( "[TestData] Shuffled rate source array", this.rateSource );

            // trim the array
            this.rateSource = _.compact( _.map( this.rateSource, function( el )
            {
                if ( --pointer >= 0 ) {
                    return el;
                }
                else {
                    return null;
                }
            } ) );

            console.info( "[TestData] Selected the following data for rating: ", this.rateSource );

        },

        /**
         * Get a new pair of Testdata instances to rate.

         * These pairs a are always different.
         *
         * @method getRateTupple
         * @returns {Array<pc.model.FacebookPicture|pc.model.FacebookStatus>} A pair to rate
         */
        getRateTupple: function()
        {
            if ( --this.remainingRates < 0 || this.alreadyComparedTuples.length > Math.pow( 2,
                this.rateSource.length ) ) {
                console.debug( '[TestData] Rating done: ', this.rateTupples );
                return null;
            }

            this.ratePtr = this.ratePtr % this.rateSource.length;

            var one = this.rateSource[ this.ratePtr++ ],
                two = null,
                alreadyseen = true;

            while ( alreadyseen || one === two ) {

                two = this.rateSource[ $.randomBetween( 0, this.rateSource.length ) ];
                if ( !two ) {
                    continue;
                }

                alreadyseen = _.contains( this.alreadyComparedTuples, parseInt( one.id, 10 ) + parseInt( two.id, 10 ) );
            }

            this.alreadyComparedTuples.push( parseInt( one.id, 10 ) + parseInt( two.id, 10 ) );

            console.debug( '[TestData] Pair is ', one, ' and ', two );

            return [one, two];

        },

        /**
         * Set the winner of a comparison.

         * The winner gets points depending on the opponent's current rating.
         *
         * @method setWinner
         * @param {pc.model.FacebookPicture|pc.model.FacebookStatus} one
         * @param {pc.model.FacebookPicture|pc.model.FacebookStatus} two
         * @param {pc.model.FacebookPicture|pc.model.FacebookStatus} winner The winner must be either one or two
         */
        setWinner: function( one, two, winner )
        {

            var oneRating = this.rateTupples[one.id] !== undefined ? this.rateTupples[one.id] : this.INITIAL_SCORE;
            var twoRating = this.rateTupples[two.id] !== undefined ? this.rateTupples[two.id] : this.INITIAL_SCORE;

            this.elo.setNewSetings( oneRating, twoRating, one === winner ? 1 : 0, two === winner ? 1 : 0 );
            var res = this.elo.getNewRatings();

            this.rateTupples[one.id] = res.a;
            this.rateTupples[two.id] = res.b;

            // attach the points to the item
            one.set( 'points', res.a );
            two.set( 'points', res.b );

            console.log( '[TestData] New scores: ', res.a, res.b );

        },

        /**
         * How many rounds are used to rate items
         *
         * @method getMaxRounds
         * @returns {number} max rounds
         */
        getMaxRounds: function()
        {
            return this.TEST_ROUNDS;
        },

        /**
         * Current round of maxRounds
         *
         * @method getCurrentRound
         * @returns {number} current round
         */
        getCurrentRound: function()
        {
            return this.TEST_ROUNDS - this.remainingRates;
        },

        /**
         * Get a list of all items ordered by their rating (desc)
         *
         * @method getOrderedList
         * @returns {Array} A array with all items ordered by points descending
         */
        getOrderedList: function()
        {

            var list = [],
                i = this.MAX_INIT_ITEMS,
                highest = 0,
                pickedId = 0,
                el,
                rateTupples = _.clone( this.rateTupples );

            while ( --i >= 0 ) {
                _.each( _.keys( rateTupples ), _.bind( function( id )
                {
                    if ( highest === 0 || highest <= rateTupples[id] ) {
                        pickedId = id;
                        highest = rateTupples[id];
                    }
                }, this ) );

                console.debug( "[TestData] Highest rating found is ", highest, pickedId );

                if ( pickedId === 0 ) {
                    console.warn( '[TestData] Picked id is 0, skipping' );
                    continue;
                }

                delete rateTupples[pickedId];

                el = this.get( 'pictures' ).get( pickedId );
                if ( !el ) el = this.get( 'statuses' ).get( pickedId );
                el.set( 'points', highest );
                list.push( el );

                console.log( "[TestData] Selected item to use: ", pickedId );

                el = undefined;
                pickedId = 0;
                highest = 0;
            }

            return list;
        },

        /**
         * Get the TEST_DATA_SIZE elements with highest score from the test data collection.

         * Sets the data attribute.
         *
         * @method extractTestData
         */
        extractTestData: function()
        {

            if ( this.get( 'data' ) !== undefined ) return;

            var data = [],
                orderedList = this.getOrderedList();

            for ( var i = this.TEST_DATA_SIZE; i > 0; i-- ) {
                data.push( orderedList.pop() );
            }

            this.set( 'data', data );
            console.log( '[TestData] The following data is select to test: ', data );

        },

        /**
         * Collect a few pictures from the player's collected picture set.

         * These pictures are later used for comparison.

         * Sets the pictures attribute
         *
         * @method _collectPictures
         * @private
         */
        _collectPictures: function()
        {
            console.log( '[TestData] Collecting pictures for test' );

            var availablePics = this.player.getPictures();
            var pickedPlain = this._genericCollector( availablePics );
            var pickedPics = new pc.model.FacebookPictureCollection( pickedPlain );

            this.set( 'pictures', pickedPics );
            console.info( '[TestData] Using the following ' + this.MAX_INIT_ITEMS + ' pictures for test:',
                this.get( 'pictures' ) );
        },

        /**
         * Collect a few statuses from the player's collected statuses set.

         * These statuses are later used for comparison.

         * Sets the statuses attribute
         *
         * @method _collectStatuses
         * @private
         */
        _collectStatuses: function()
        {
            console.log( '[TestData] Collecting statuses for test' );

            var availableStatuses = this.player.getStatuses();
            var pickedPlain = this._genericCollector( availableStatuses );
            var pickedStatuses = new pc.model.FacebookStatusCollection( pickedPlain );

            this.set( 'statuses', pickedStatuses );
            console.info( '[TestData] Using the following ' + this.MAX_INIT_ITEMS + ' statuses for test:',
                this.get( 'statuses' ) );
        },

        /**
         * Randomly select MAX_INIT_ITEMS from arr.
         *
         * @method _genericCollector
         * @param {Array<pc.model.FacebookPicture|pc.model.FacebookStatus>} arr The source arr to select from
         * @returns {Array} A arr with MAX_INIT_ITEMS items from arr
         * @private
         */
        _genericCollector: function( arr )
        {

            var picked = [];

            if ( arr.length <= this.TEST_DATA_SIZE ) {
                console.error( "[TestData] Insufficient data", arr.length, this.TEST_DATA_SIZE );
                throw new pc.common.Exception( {caption: "insufficient data"} );
            }

            var i = arr.length < this.MAX_INIT_ITEMS ? arr.length : this.MAX_INIT_ITEMS;

            console.debug( '[TestData] available ' + arr.length + ' to pick ' + i );

            while ( i-- > 0 ) {
                var pic = arr.at( $.randomBetween( 0, arr.length ) );

                if ( pic && pic.get( 'privacy' ).get( 'level' ) !== pc.common.PrivacyDefinition.Level.NOBODY && !_.contains( picked,
                    pic ) ) {
                    picked.push( pic );
                }
                else {
                    ++i;
                }
            }

            return picked;

        }

    }, {

        /**
         * @property { {String: Number}} scope Enum defining the game scope
         */
        Scope: {
            IMAGES: 0, STATUSES: 1, BOTH: 2
        },

        /**
         * Get a singleton instance of test data.
         *
         * @method getInstance
         * @returns {pc.model.TestData} A singleton instance
         * @static
         */
        getInstance: function()
        {
            if ( this.__instance === undefined ) {
                this.__instance = new pc.model.TestData();
            }
            return this.__instance;
        }
    } );

})();<commit_msg>Fix: extracted TestData were the items with the lowest rating instead the ones' with highest<commit_after>(function()
{
    "use strict";

    var ns = namespace( "pc.model" );

    /**
     * A testdata set.

     * Testdata represents a set of pictures and statuses used to play
     *
     * @namespace pc.model
     * @class TestData
     * @extends Backbone.Model
     */
    ns.TestData = Backbone.Model.extend( {

        MAX_INIT_ITEMS: 12,
        TEST_ROUNDS:    20,
        INITIAL_SCORE:  1000,
        TEST_DATA_SIZE: 5,

        /**
         * Create a new set of testdata
         *
         * @method
         * @constructor
         */
        initialize: function()
        {
            this.player = pc.model.FacebookPlayer.getInstance();
            this.set( 'pictures', new pc.model.FacebookPictureCollection() );
            this.set( 'statuses', new pc.model.FacebookStatusCollection() );
            this.set( 'data', undefined );

            this.rateTupples = {};
            this.alreadyComparedTuples = [];
            this.ratePtr = 0;
            this.elo = new EloRating(); // NOQA
            this.remainingRates = this.TEST_ROUNDS;
            this.rateSource = [];
        },

        /**
         * Set the scope for the game.

         * Can be set to use images or statuses only or a combination of both
         *
         * @method setScope
         * @param {pc.model.TestData.Scope} scope The scope to use
         * @throws "E_UNKNOWN_SCOPE" If the scope is unknown
         */
        setScope: function( scope )
        {
            switch ( scope ) {
                case pc.model.TestData.Scope.IMAGES:
                    this._collectPictures();
                    break;
                case pc.model.TestData.Scope.STATUSES:
                    this._collectStatuses();
                    break;
                case pc.model.TestData.Scope.BOTH:
                    this._collectPictures();
                    this._collectStatuses();
                    break;
                default:
                    console.error( "[TestData] Unknown game scope:", scope );
                    throw "E_UNKNOWN_SCOPE";
            }

            console.debug( "[TestData] Collected data", this.rateSource, "with scope", scope );

            var pointer = this.MAX_INIT_ITEMS;
            this.rateSource = _.union( this.get( 'pictures' ).models, this.get( 'statuses' ).models );
            this.rateSource = _.shuffle( this.rateSource );
            console.debug( "[TestData] Shuffled rate source array", this.rateSource );

            // trim the array
            this.rateSource = _.compact( _.map( this.rateSource, function( el )
            {
                if ( --pointer >= 0 ) {
                    return el;
                }
                else {
                    return null;
                }
            } ) );

            console.info( "[TestData] Selected the following data for rating: ", this.rateSource );

        },

        /**
         * Get a new pair of Testdata instances to rate.

         * These pairs a are always different.
         *
         * @method getRateTupple
         * @returns {Array<pc.model.FacebookPicture|pc.model.FacebookStatus>} A pair to rate
         */
        getRateTupple: function()
        {
            if ( --this.remainingRates < 0 || this.alreadyComparedTuples.length > Math.pow( 2,
                this.rateSource.length ) ) {
                console.debug( '[TestData] Rating done: ', this.rateTupples );
                return null;
            }

            this.ratePtr = this.ratePtr % this.rateSource.length;

            var one = this.rateSource[ this.ratePtr++ ],
                two = null,
                alreadyseen = true;

            while ( alreadyseen || one === two ) {

                two = this.rateSource[ $.randomBetween( 0, this.rateSource.length ) ];
                if ( !two ) {
                    continue;
                }

                alreadyseen = _.contains( this.alreadyComparedTuples, parseInt( one.id, 10 ) + parseInt( two.id, 10 ) );
            }

            this.alreadyComparedTuples.push( parseInt( one.id, 10 ) + parseInt( two.id, 10 ) );

            console.debug( '[TestData] Pair is ', one, ' and ', two );

            return [one, two];

        },

        /**
         * Set the winner of a comparison.

         * The winner gets points depending on the opponent's current rating.
         *
         * @method setWinner
         * @param {pc.model.FacebookPicture|pc.model.FacebookStatus} one
         * @param {pc.model.FacebookPicture|pc.model.FacebookStatus} two
         * @param {pc.model.FacebookPicture|pc.model.FacebookStatus} winner The winner must be either one or two
         */
        setWinner: function( one, two, winner )
        {

            var oneRating = this.rateTupples[one.id] !== undefined ? this.rateTupples[one.id] : this.INITIAL_SCORE;
            var twoRating = this.rateTupples[two.id] !== undefined ? this.rateTupples[two.id] : this.INITIAL_SCORE;

            this.elo.setNewSetings( oneRating, twoRating, one === winner ? 1 : 0, two === winner ? 1 : 0 );
            var res = this.elo.getNewRatings();

            this.rateTupples[one.id] = res.a;
            this.rateTupples[two.id] = res.b;

            // attach the points to the item
            one.set( 'points', res.a );
            two.set( 'points', res.b );

            console.log( '[TestData] New scores: ', res.a, res.b );

        },

        /**
         * How many rounds are used to rate items
         *
         * @method getMaxRounds
         * @returns {number} max rounds
         */
        getMaxRounds: function()
        {
            return this.TEST_ROUNDS;
        },

        /**
         * Current round of maxRounds
         *
         * @method getCurrentRound
         * @returns {number} current round
         */
        getCurrentRound: function()
        {
            return this.TEST_ROUNDS - this.remainingRates;
        },

        /**
         * Get a list of all items ordered by their rating (desc)
         *
         * @method getOrderedList
         * @returns {Array} A array with all items ordered by points descending
         */
        getOrderedList: function()
        {

            var list = [],
                i = this.MAX_INIT_ITEMS,
                highest = 0,
                pickedId = 0,
                el,
                rateTupples = _.clone( this.rateTupples );

            while ( --i >= 0 ) {
                _.each( _.keys( rateTupples ), _.bind( function( id )
                {
                    if ( highest === 0 || highest <= rateTupples[id] ) {
                        pickedId = id;
                        highest = rateTupples[id];
                    }
                }, this ) );

                console.debug( "[TestData] Highest rating found is ", highest, pickedId );

                if ( pickedId === 0 ) {
                    console.warn( '[TestData] Picked id is 0, skipping' );
                    continue;
                }

                delete rateTupples[pickedId];

                el = this.get( 'pictures' ).get( pickedId );
                if ( !el ) el = this.get( 'statuses' ).get( pickedId );
                el.set( 'points', highest );
                list.push( el );

                console.log( "[TestData] Selected item to use: ", pickedId );

                el = undefined;
                pickedId = 0;
                highest = 0;
            }

            return list;
        },

        /**
         * Get the TEST_DATA_SIZE elements with highest score from the test data collection.

         * Sets the data attribute.
         *
         * @method extractTestData
         */
        extractTestData: function()
        {

            if ( this.get( 'data' ) !== undefined ) return;

            var data = [],
                orderedList = this.getOrderedList();

            for ( var i = this.TEST_DATA_SIZE; i > 0; i-- ) {
                data.push( orderedList.shift() );
            }

            this.set( 'data', data );
            console.log( '[TestData] The following data is select to test: ', data );

        },

        /**
         * Collect a few pictures from the player's collected picture set.

         * These pictures are later used for comparison.

         * Sets the pictures attribute
         *
         * @method _collectPictures
         * @private
         */
        _collectPictures: function()
        {
            console.log( '[TestData] Collecting pictures for test' );

            var availablePics = this.player.getPictures();
            var pickedPlain = this._genericCollector( availablePics );
            var pickedPics = new pc.model.FacebookPictureCollection( pickedPlain );

            this.set( 'pictures', pickedPics );
            console.info( '[TestData] Using the following ' + this.MAX_INIT_ITEMS + ' pictures for test:',
                this.get( 'pictures' ) );
        },

        /**
         * Collect a few statuses from the player's collected statuses set.

         * These statuses are later used for comparison.

         * Sets the statuses attribute
         *
         * @method _collectStatuses
         * @private
         */
        _collectStatuses: function()
        {
            console.log( '[TestData] Collecting statuses for test' );

            var availableStatuses = this.player.getStatuses();
            var pickedPlain = this._genericCollector( availableStatuses );
            var pickedStatuses = new pc.model.FacebookStatusCollection( pickedPlain );

            this.set( 'statuses', pickedStatuses );
            console.info( '[TestData] Using the following ' + this.MAX_INIT_ITEMS + ' statuses for test:',
                this.get( 'statuses' ) );
        },

        /**
         * Randomly select MAX_INIT_ITEMS from arr.
         *
         * @method _genericCollector
         * @param {Array<pc.model.FacebookPicture|pc.model.FacebookStatus>} arr The source arr to select from
         * @returns {Array} A arr with MAX_INIT_ITEMS items from arr
         * @private
         */
        _genericCollector: function( arr )
        {

            var picked = [];

            if ( arr.length <= this.TEST_DATA_SIZE ) {
                console.error( "[TestData] Insufficient data", arr.length, this.TEST_DATA_SIZE );
                throw new pc.common.Exception( {caption: "insufficient data"} );
            }

            var i = arr.length < this.MAX_INIT_ITEMS ? arr.length : this.MAX_INIT_ITEMS;

            console.debug( '[TestData] available ' + arr.length + ' to pick ' + i );

            while ( i-- > 0 ) {
                var pic = arr.at( $.randomBetween( 0, arr.length ) );

                if ( pic && pic.get( 'privacy' ).get( 'level' ) !== pc.common.PrivacyDefinition.Level.NOBODY && !_.contains( picked,
                    pic ) ) {
                    picked.push( pic );
                }
                else {
                    ++i;
                }
            }

            return picked;

        }

    }, {

        /**
         * @property { {String: Number}} scope Enum defining the game scope
         */
        Scope: {
            IMAGES: 0, STATUSES: 1, BOTH: 2
        },

        /**
         * Get a singleton instance of test data.
         *
         * @method getInstance
         * @returns {pc.model.TestData} A singleton instance
         * @static
         */
        getInstance: function()
        {
            if ( this.__instance === undefined ) {
                this.__instance = new pc.model.TestData();
            }
            return this.__instance;
        }
    } );

})();<|endoftext|>