<commit_before>var WinChan = (function() {
  var RELAY_FRAME_NAME = "__winchan_relay_frame";
  var CLOSE_CMD = "die";

  // a portable addListener implementation
  function addListener(w, event, cb) {
    if(w.attachEvent) w.attachEvent('on' + event, cb);
    else if (w.addEventListener) w.addEventListener(event, cb, false);
  }

  // a portable removeListener implementation
  function removeListener(w, event, cb) {
    if(w.detachEvent) w.detachEvent('on' + event, cb);
    else if (w.removeEventListener) w.removeEventListener(event, cb, false);
  }

  // checking for IE8 or above
  function isInternetExplorer() {
    var rv = -1; // Return value assumes failure.
    var ua = navigator.userAgent;
    if (navigator.appName === 'Microsoft Internet Explorer') {
      var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
      if (re.exec(ua) != null)
        rv = parseFloat(RegExp.$1);
    }
    // IE > 11
    else if (ua.indexOf("Trident") > -1) {
      var re = new RegExp("rv:([0-9]{2,2}[\.0-9]{0,})");
      if (re.exec(ua) !== null) {
        rv = parseFloat(RegExp.$1);
      }
    }

    return rv >= 8;
  }

  // checking Mobile Firefox (Fennec)
  function isFennec() {
    try {
      // We must check for both XUL and Java versions of Fennec.  Both have
      // distinct UA strings.
      var userAgent = navigator.userAgent;
      return (userAgent.indexOf('Fennec/') != -1) ||  // XUL
             (userAgent.indexOf('Firefox/') != -1 && userAgent.indexOf('Android') != -1);   // Java
    } catch(e) {}
    return false;
  }

  // feature checking to see if this platform is supported at all
  function isSupported() {
    return (window.JSON && window.JSON.stringify &&
            window.JSON.parse && window.postMessage);
  }

  // given a URL, extract the origin
  function extractOrigin(url) {
    if (!/^https?:\/\//.test(url)) url = window.location.href;
    var a = document.createElement('a');
    a.href = url;
    return a.protocol + "//" + a.host;
  }

  // find the relay iframe in the opener
  function findRelay() {
    var loc = window.location;
    var frames = window.opener.frames;
    for (var i = frames.length - 1; i >= 0; i--) {
      try {
        if (frames[i].location.protocol === window.location.protocol &&
            frames[i].location.host === window.location.host &&
            frames[i].name === RELAY_FRAME_NAME)
        {
          return frames[i];
        }
      } catch(e) { }
    }
    return;
  }

  var isIE = isInternetExplorer();

  if (isSupported()) {
    /*  General flow:
     *                  0. user clicks
     *  (IE SPECIFIC)   1. caller adds relay iframe (served from trusted domain) to DOM
     *                  2. caller opens window (with content from trusted domain)
     *                  3. window on opening adds a listener to 'message'
     *  (IE SPECIFIC)   4. window on opening finds iframe
     *                  5. window checks if iframe is "loaded" - has a 'doPost' function yet
     *  (IE SPECIFIC5)  5a. if iframe.doPost exists, window uses it to send ready event to caller
     *  (IE SPECIFIC5)  5b. if iframe.doPost doesn't exist, window waits for frame ready
     *  (IE SPECIFIC5)  5bi. once ready, window calls iframe.doPost to send ready event
     *                  6. caller upon reciept of 'ready', sends args
     */
    return {
      open: function(opts, cb) {
        if (!cb) throw "missing required callback argument";

        // test required options
        var err;
        if (!opts.url) err = "missing required 'url' parameter";
        if (!opts.relay_url) err = "missing required 'relay_url' parameter";
        if (err) setTimeout(function() { cb(err); }, 0);

        // supply default options
        if (!opts.window_name) opts.window_name = null;
        if (!opts.window_features || isFennec()) opts.window_features = undefined;

        // opts.params may be undefined

        var iframe;

        // sanity check, are url and relay_url the same origin?
        var origin = extractOrigin(opts.url);
        if (origin !== extractOrigin(opts.relay_url)) {
          return setTimeout(function() {
            cb('invalid arguments: origin of url and relay_url must match');
          }, 0);
        }

        var messageTarget;

        if (isIE) {
          // first we need to add a "relay" iframe to the document that's served
          // from the target domain.  We can postmessage into a iframe, but not a
          // window
          iframe = document.createElement("iframe");
          // iframe.setAttribute('name', framename);
          iframe.setAttribute('src', opts.relay_url);
          iframe.style.display = "none";
          iframe.setAttribute('name', RELAY_FRAME_NAME);
          document.body.appendChild(iframe);
          messageTarget = iframe.contentWindow;
        }

        var w = window.open(opts.url, opts.window_name, opts.window_features);

        if (!messageTarget) messageTarget = w;

        // lets listen in case the window blows up before telling us
        var closeInterval = setInterval(function() {
          if (w && w.closed) {
            cleanup();
            if (cb) {
              cb('User closed the popup window');
              cb = null;
            }
          }
        }, 500);

        var req = JSON.stringify({a: 'request', d: opts.params});

        // cleanup on unload
        function cleanup() {
          if (iframe) document.body.removeChild(iframe);
          iframe = undefined;
          if (closeInterval) closeInterval = clearInterval(closeInterval);
          removeListener(window, 'message', onMessage);
          removeListener(window, 'unload', cleanup);
          if (w) {
            try {
              w.close();
            } catch (securityViolation) {
              // This happens in Opera 12 sometimes
              // see https://github.com/mozilla/browserid/issues/1844
              messageTarget.postMessage(CLOSE_CMD, origin);
            }
          }
          w = messageTarget = undefined;
        }

        addListener(window, 'unload', cleanup);

        function onMessage(e) {
          if (e.origin !== origin) { return; }
          try {
            var d = JSON.parse(e.data);
            if (d.a === 'ready') messageTarget.postMessage(req, origin);
            else if (d.a === 'error') {
              cleanup();
              if (cb) {
                cb(d.d);
                cb = null;
              }
            } else if (d.a === 'response') {
              cleanup();
              if (cb) {
                cb(null, d.d);
                cb = null;
              }
            }
          } catch(err) { }
        }

        addListener(window, 'message', onMessage);

        return {
          close: cleanup,
          focus: function() {
            if (w) {
              try {
                w.focus();
              } catch (e) {
                // IE7 blows up here, do nothing
              }
            }
          }
        };
      },
      onOpen: function(cb) {
        var o = "*";
        var msgTarget = isIE ? findRelay() : window.opener;
        if (!msgTarget) throw "can't find relay frame";
        function doPost(msg) {
          msg = JSON.stringify(msg);
          if (isIE) msgTarget.doPost(msg, o);
          else msgTarget.postMessage(msg, o);
        }

        function onMessage(e) {
          // only one message gets through, but let's make sure it's actually
          // the message we're looking for (other code may be using
          // postmessage) - we do this by ensuring the payload can
          // be parsed, and it's got an 'a' (action) value of 'request'.
          var d;
          try {
            d = JSON.parse(e.data);
          } catch(err) { }
          if (!d || d.a !== 'request') return;
          removeListener(window, 'message', onMessage);
          o = e.origin;
          if (cb) {
            // this setTimeout is critically important for IE8 -
            // in ie8 sometimes addListener for 'message' can synchronously
            // cause your callback to be invoked.  awesome.
            setTimeout(function() {
              cb(o, d.d, function(r) {
                cb = undefined;
                doPost({a: 'response', d: r});
              });
            }, 0);
          }
        }

        function onDie(e) {
          if (e.data === CLOSE_CMD) {
            try { window.close(); } catch (o_O) {}
          }
        }
        addListener(isIE ? msgTarget : window, 'message', onMessage);
        addListener(isIE ? msgTarget : window, 'message', onDie);

        // we cannot post to our parent that we're ready before the iframe
        // is loaded. (IE specific possible failure)
        try {
          doPost({a: "ready"});
        } catch(e) {
          // this code should never be exectued outside IE
          addListener(msgTarget, 'load', function(e) {
            doPost({a: "ready"});
          });
        }

        // if window is unloaded and the client hasn't called cb, it's an error
        var onUnload = function() {
          try {
            // IE8 doesn't like this...
            removeListener(isIE ? msgTarget : window, 'message', onDie);
          } catch (ohWell) { }
          if (cb) doPost({ a: 'error', d: 'client closed window' });
          cb = undefined;
          // explicitly close the window, in case the client is trying to reload or nav
          try { window.close(); } catch (e) { }
        };
        addListener(window, 'unload', onUnload);
        return {
          detach: function() {
            removeListener(window, 'unload', onUnload);
          }
        };
      }
    };
  } else {
    return {
      open: function(url, winopts, arg, cb) {
        setTimeout(function() { cb("unsupported browser"); }, 0);
      },
      onOpen: function(cb) {
        setTimeout(function() { cb("unsupported browser"); }, 0);
      }
    };
  }
})();

if (module && module.exports) {
  module.exports = WinChan;
}
<commit_msg>Better undefined check for module.exports.<commit_after>var WinChan = (function() {
  var RELAY_FRAME_NAME = "__winchan_relay_frame";
  var CLOSE_CMD = "die";

  // a portable addListener implementation
  function addListener(w, event, cb) {
    if(w.attachEvent) w.attachEvent('on' + event, cb);
    else if (w.addEventListener) w.addEventListener(event, cb, false);
  }

  // a portable removeListener implementation
  function removeListener(w, event, cb) {
    if(w.detachEvent) w.detachEvent('on' + event, cb);
    else if (w.removeEventListener) w.removeEventListener(event, cb, false);
  }

  // checking for IE8 or above
  function isInternetExplorer() {
    var rv = -1; // Return value assumes failure.
    var ua = navigator.userAgent;
    if (navigator.appName === 'Microsoft Internet Explorer') {
      var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
      if (re.exec(ua) != null)
        rv = parseFloat(RegExp.$1);
    }
    // IE > 11
    else if (ua.indexOf("Trident") > -1) {
      var re = new RegExp("rv:([0-9]{2,2}[\.0-9]{0,})");
      if (re.exec(ua) !== null) {
        rv = parseFloat(RegExp.$1);
      }
    }

    return rv >= 8;
  }

  // checking Mobile Firefox (Fennec)
  function isFennec() {
    try {
      // We must check for both XUL and Java versions of Fennec.  Both have
      // distinct UA strings.
      var userAgent = navigator.userAgent;
      return (userAgent.indexOf('Fennec/') != -1) ||  // XUL
             (userAgent.indexOf('Firefox/') != -1 && userAgent.indexOf('Android') != -1);   // Java
    } catch(e) {}
    return false;
  }

  // feature checking to see if this platform is supported at all
  function isSupported() {
    return (window.JSON && window.JSON.stringify &&
            window.JSON.parse && window.postMessage);
  }

  // given a URL, extract the origin
  function extractOrigin(url) {
    if (!/^https?:\/\//.test(url)) url = window.location.href;
    var a = document.createElement('a');
    a.href = url;
    return a.protocol + "//" + a.host;
  }

  // find the relay iframe in the opener
  function findRelay() {
    var loc = window.location;
    var frames = window.opener.frames;
    for (var i = frames.length - 1; i >= 0; i--) {
      try {
        if (frames[i].location.protocol === window.location.protocol &&
            frames[i].location.host === window.location.host &&
            frames[i].name === RELAY_FRAME_NAME)
        {
          return frames[i];
        }
      } catch(e) { }
    }
    return;
  }

  var isIE = isInternetExplorer();

  if (isSupported()) {
    /*  General flow:
     *                  0. user clicks
     *  (IE SPECIFIC)   1. caller adds relay iframe (served from trusted domain) to DOM
     *                  2. caller opens window (with content from trusted domain)
     *                  3. window on opening adds a listener to 'message'
     *  (IE SPECIFIC)   4. window on opening finds iframe
     *                  5. window checks if iframe is "loaded" - has a 'doPost' function yet
     *  (IE SPECIFIC5)  5a. if iframe.doPost exists, window uses it to send ready event to caller
     *  (IE SPECIFIC5)  5b. if iframe.doPost doesn't exist, window waits for frame ready
     *  (IE SPECIFIC5)  5bi. once ready, window calls iframe.doPost to send ready event
     *                  6. caller upon reciept of 'ready', sends args
     */
    return {
      open: function(opts, cb) {
        if (!cb) throw "missing required callback argument";

        // test required options
        var err;
        if (!opts.url) err = "missing required 'url' parameter";
        if (!opts.relay_url) err = "missing required 'relay_url' parameter";
        if (err) setTimeout(function() { cb(err); }, 0);

        // supply default options
        if (!opts.window_name) opts.window_name = null;
        if (!opts.window_features || isFennec()) opts.window_features = undefined;

        // opts.params may be undefined

        var iframe;

        // sanity check, are url and relay_url the same origin?
        var origin = extractOrigin(opts.url);
        if (origin !== extractOrigin(opts.relay_url)) {
          return setTimeout(function() {
            cb('invalid arguments: origin of url and relay_url must match');
          }, 0);
        }

        var messageTarget;

        if (isIE) {
          // first we need to add a "relay" iframe to the document that's served
          // from the target domain.  We can postmessage into a iframe, but not a
          // window
          iframe = document.createElement("iframe");
          // iframe.setAttribute('name', framename);
          iframe.setAttribute('src', opts.relay_url);
          iframe.style.display = "none";
          iframe.setAttribute('name', RELAY_FRAME_NAME);
          document.body.appendChild(iframe);
          messageTarget = iframe.contentWindow;
        }

        var w = window.open(opts.url, opts.window_name, opts.window_features);

        if (!messageTarget) messageTarget = w;

        // lets listen in case the window blows up before telling us
        var closeInterval = setInterval(function() {
          if (w && w.closed) {
            cleanup();
            if (cb) {
              cb('User closed the popup window');
              cb = null;
            }
          }
        }, 500);

        var req = JSON.stringify({a: 'request', d: opts.params});

        // cleanup on unload
        function cleanup() {
          if (iframe) document.body.removeChild(iframe);
          iframe = undefined;
          if (closeInterval) closeInterval = clearInterval(closeInterval);
          removeListener(window, 'message', onMessage);
          removeListener(window, 'unload', cleanup);
          if (w) {
            try {
              w.close();
            } catch (securityViolation) {
              // This happens in Opera 12 sometimes
              // see https://github.com/mozilla/browserid/issues/1844
              messageTarget.postMessage(CLOSE_CMD, origin);
            }
          }
          w = messageTarget = undefined;
        }

        addListener(window, 'unload', cleanup);

        function onMessage(e) {
          if (e.origin !== origin) { return; }
          try {
            var d = JSON.parse(e.data);
            if (d.a === 'ready') messageTarget.postMessage(req, origin);
            else if (d.a === 'error') {
              cleanup();
              if (cb) {
                cb(d.d);
                cb = null;
              }
            } else if (d.a === 'response') {
              cleanup();
              if (cb) {
                cb(null, d.d);
                cb = null;
              }
            }
          } catch(err) { }
        }

        addListener(window, 'message', onMessage);

        return {
          close: cleanup,
          focus: function() {
            if (w) {
              try {
                w.focus();
              } catch (e) {
                // IE7 blows up here, do nothing
              }
            }
          }
        };
      },
      onOpen: function(cb) {
        var o = "*";
        var msgTarget = isIE ? findRelay() : window.opener;
        if (!msgTarget) throw "can't find relay frame";
        function doPost(msg) {
          msg = JSON.stringify(msg);
          if (isIE) msgTarget.doPost(msg, o);
          else msgTarget.postMessage(msg, o);
        }

        function onMessage(e) {
          // only one message gets through, but let's make sure it's actually
          // the message we're looking for (other code may be using
          // postmessage) - we do this by ensuring the payload can
          // be parsed, and it's got an 'a' (action) value of 'request'.
          var d;
          try {
            d = JSON.parse(e.data);
          } catch(err) { }
          if (!d || d.a !== 'request') return;
          removeListener(window, 'message', onMessage);
          o = e.origin;
          if (cb) {
            // this setTimeout is critically important for IE8 -
            // in ie8 sometimes addListener for 'message' can synchronously
            // cause your callback to be invoked.  awesome.
            setTimeout(function() {
              cb(o, d.d, function(r) {
                cb = undefined;
                doPost({a: 'response', d: r});
              });
            }, 0);
          }
        }

        function onDie(e) {
          if (e.data === CLOSE_CMD) {
            try { window.close(); } catch (o_O) {}
          }
        }
        addListener(isIE ? msgTarget : window, 'message', onMessage);
        addListener(isIE ? msgTarget : window, 'message', onDie);

        // we cannot post to our parent that we're ready before the iframe
        // is loaded. (IE specific possible failure)
        try {
          doPost({a: "ready"});
        } catch(e) {
          // this code should never be exectued outside IE
          addListener(msgTarget, 'load', function(e) {
            doPost({a: "ready"});
          });
        }

        // if window is unloaded and the client hasn't called cb, it's an error
        var onUnload = function() {
          try {
            // IE8 doesn't like this...
            removeListener(isIE ? msgTarget : window, 'message', onDie);
          } catch (ohWell) { }
          if (cb) doPost({ a: 'error', d: 'client closed window' });
          cb = undefined;
          // explicitly close the window, in case the client is trying to reload or nav
          try { window.close(); } catch (e) { }
        };
        addListener(window, 'unload', onUnload);
        return {
          detach: function() {
            removeListener(window, 'unload', onUnload);
          }
        };
      }
    };
  } else {
    return {
      open: function(url, winopts, arg, cb) {
        setTimeout(function() { cb("unsupported browser"); }, 0);
      },
      onOpen: function(cb) {
        setTimeout(function() { cb("unsupported browser"); }, 0);
      }
    };
  }
})();

if (typeof module !== 'undefined' && module.exports) {
  module.exports = WinChan;
}
<|endoftext|>


<commit_before>'use strict';

var fs = require('fs');
var es = require('event-stream');
var path = require('path');
var gutil = require('gulp-util');
var extname = require('../extname');
var transform = require('../transform');
var tags = require('../tags');
var PluginError = gutil.PluginError;
var File = gutil.File;
var magenta = gutil.colors.magenta;
var cyan = gutil.colors.cyan;
var red = gutil.colors.red;

/**
 * Constants
 */
var PLUGIN_NAME = 'gulp-inject';

module.exports = exports = function(sources, opt){
  if (!sources) {
    throw error('Missing sources stream!');
  }
  if (!opt) {
    opt = {};
  }

  if (opt.sort) {
    throw error('sort option is deprecated! Use `sort-stream` module instead!');
  }
  if (opt.templateString) {
    throw error('`templateString` option is deprecated! Create a virtual `vinyl` file instead!');
  }
  if (opt.transform && typeof opt.transform !== 'function') {
    throw error('transform option must be a function');
  }

  // Notify people of common mistakes...
  if (opt.read) {
    warn('There is no ' + magenta('`read`') + ' option. Did you mean to provide it for ' + magenta('`gulp.src`') + ' perhaps?');
  }

  // Defaults:
  opt.quiet = bool(opt, 'quiet', false);
  opt.ignorePath = toArray(opt.ignorePath).map(unixify);
  opt.relative = bool(opt, 'relative', false);
  opt.addRootSlash = bool(opt, 'addRootSlash', !opt.relative);
  opt.transform = defaults(opt, 'transform', transform);
  opt.tags = tags();
  opt.tags.name = defaults(opt, 'name', 'inject');
  transform.selfClosingTag = bool(opt, 'selfClosingTag', false);

  // Is the first parameter a Vinyl File Stream:
  if (typeof sources.on === 'function' && typeof sources.pipe === 'function') {
    return handleVinylStream(sources, opt);
  }

  throw error('passing target file as a string is deprecated! Pass a vinyl file stream (i.e. use `gulp.src`)!');
};

function defaults (options, prop, defaultValue) {
  return options[prop] || defaultValue;
}

function bool (options, prop, defaultVal) {
  return typeof options[prop] !== 'undefined' ? !!options[prop] : defaultVal;
}

/**
 * Handle injection when files to
 * inject comes from a Vinyl File Stream
 *
 * @param {Stream} sources
 * @param {Object} opt
 * @returns {Stream}
 */
function handleVinylStream (sources, opt) {
  var collected = collectFilesToInject(sources, opt);

  return es.map(function (target, cb) {
    if (target.isStream()) {
      return cb(error('Streams not supported for target templates!'));
    }
    collected(function (collection) {
      target.contents = getNewContent(target, collection, opt);
      cb(null, target);
    });
  });
}

/**
 * Collecting files to inject from Vinyl File Stream
 *
 * Returns an almost promise like function which can be
 * called multiple times with a callback, that will be
 * resolved with the result of the file collection.
 *
 * @param {Stream} sources
 * @param {Object} opt
 * @returns {Function}
 */
function collectFilesToInject (sources, opt) {
  var collection = [], done = false, queue = [];

  sources.pipe(es.through(collector(collection, opt), function () {
    done = true;
    while (queue.length) {
      resolve(queue.shift());
    }
  }));

  function resolve (cb) {
    setImmediate(function () {
      cb(collection);
    });
  }

  return function (cb) {
    if (done) {
      resolve(cb);
    } else {
      queue.push(cb);
    }
  };
}

/**
 * Create a file collecting function
 * to be used in es.through
 *
 * @param {Array} collection  Collection to fill with files
 * @param {Object} opt
 * @returns {Function}
 */
function collector (collection, opt) {
  return function (file) {
    if (!file.path) {
      return;
    }

    collection.push(file);
  };
}

/**
 * Get new content for template
 * with all injections made
 *
 * @param {Object} target
 * @param {Array} collection
 * @param {Object} opt
 * @returns {Buffer}
 */
function getNewContent (target, collection, opt) {
  var oldContent = target.contents;
  if (!collection.length) {
    if (!opt.quiet) {
      log('Nothing to inject into ' + magenta(target.relative) + '.');
    }
    return oldContent;
  }
  var tags = {};
  var targetExt = extname(target.path);

  var filesPerTags = groupBy(collection, function (file) {
    var ext = extname(file.path);
    var startTag = opt.tags.start(targetExt, ext, opt.starttag);
    var endTag = opt.tags.end(targetExt, ext, opt.endtag);
    var tag = startTag + endTag;
    if (!tags[tag]) {
      tags[tag] = {start: startTag, end: endTag};
    }
    return tag;
  });

  var startAndEndTags = Object.keys(filesPerTags);

  if (!opt.quiet) {
    log(cyan(collection.length) + ' files into ' + magenta(target.relative) + '.');
  }

  return new Buffer(startAndEndTags.reduce(function eachInCollection (contents, tag) {
    var files = filesPerTags[tag];
    var startTag = tags[tag].start;
    var endTag = tags[tag].end;

    return contents.replace(
      getInjectorTagsRegExp(startTag, endTag),
      function injector (match, starttag, indent, content, endtag) {
        var starttagArray = opt.removeTags ? [] : [starttag];
        var endtagArray = opt.removeTags ? [] : [endtag];
        return starttagArray
          .concat(files.reduce(function transformFile (lines, file, i) {
            var filepath = getFilepath(file, target, opt);
            var transformedContents = opt.transform(filepath, file, i, files.length, target);
            if (typeof transformedContents !== 'string') {
              return lines;
            }
            return lines.concat(transformedContents);
          }, []))
          .concat(endtagArray)
          .join(indent);
      }
    );
  }, String(oldContent)));
}

function getFilepath (sourceFile, targetFile, opt) {
  var base = opt.relative ? path.dirname(targetFile.path) : sourceFile.cwd;

  var filepath = unixify(path.relative(base, sourceFile.path));

  if (opt.ignorePath.length) {
    filepath = removeBasePath(opt.ignorePath, filepath);
  }

  if (opt.addPrefix) {
    filepath = addPrefix(filepath, opt.addPrefix);
  }

  if (opt.addRootSlash) {
    filepath = addRootSlash(filepath);
  } else if(!opt.addPrefix) {
    filepath = removeRootSlash(filepath);
  }

  if (opt.addSuffix) {
    filepath = addSuffix(filepath, opt.addSuffix);
  }

  return filepath;
}

function getTag (tag, ext) {
  return tag.replace('{{ext}}', ext);
}

function getInjectorTagsRegExp (starttag, endtag) {
  return new RegExp('(' + makeWhiteSpaceOptional(escapeForRegExp(starttag)) + ')(\\s*)(\\n|\\r|.)*?(' + makeWhiteSpaceOptional(escapeForRegExp(endtag)) + ')', 'gi');
}

function makeWhiteSpaceOptional (str) {
  return str.replace(/\s+/g, '\\s*');
}

function escapeForRegExp (str) {
  return str.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
}

function unixify (filepath) {
  return filepath.replace(/\\/g, '/');
}
function addRootSlash (filepath) {
  return filepath.replace(/^\/*([^\/])/, '/$1');
}
function removeRootSlash (filepath) {
  return filepath.replace(/^\/+/, '');
}
function addPrefix (filepath, prefix) {
  return  prefix + addRootSlash(filepath);
}
function addSuffix (filepath, suffix) {
  return  filepath + suffix;
}

function removeBasePath (basedir, filepath) {
  return toArray(basedir).reduce(function (path, remove) {
    if (path[0] === '/' && remove[0] !== '/') {
      remove = '/' + remove;
    }
    if (path[0] !== '/' && remove[0] === '/') {
      path = '/' + path;
    }
    if (remove && path.indexOf(remove) === 0) {
      return path.slice(remove.length);
    }
    return path;
  }, filepath);
}

function toArray (arr) {
  if (!Array.isArray(arr)) {
    return arr ? [arr] : [];
  }
  return arr;
}

function groupBy (arr, cb) {
  var result = {};
  for (var i = 0; i < arr.length; i++) {
    var key = cb(arr[i]);
    if (!result[key]) {
      result[key] = [];
    }
    result[key].push(arr[i]);
  }
  return result;
}

function log (message) {
  gutil.log(magenta(PLUGIN_NAME), message);
}

function warn (message) {
  log(red('WARNING') + ' ' + message);
}

function error (message) {
  return new PluginError(PLUGIN_NAME, message);
}
<commit_msg>fixed addPrefix, is broken when also using addSlashRoot = false<commit_after>'use strict';

var fs = require('fs');
var es = require('event-stream');
var path = require('path');
var gutil = require('gulp-util');
var extname = require('../extname');
var transform = require('../transform');
var tags = require('../tags');
var PluginError = gutil.PluginError;
var File = gutil.File;
var magenta = gutil.colors.magenta;
var cyan = gutil.colors.cyan;
var red = gutil.colors.red;

/**
 * Constants
 */
var PLUGIN_NAME = 'gulp-inject';

module.exports = exports = function(sources, opt) {
    if (!sources) {
        throw error('Missing sources stream!');
    }
    if (!opt) {
        opt = {};
    }

    if (opt.sort) {
        throw error('sort option is deprecated! Use `sort-stream` module instead!');
    }
    if (opt.templateString) {
        throw error('`templateString` option is deprecated! Create a virtual `vinyl` file instead!');
    }
    if (opt.transform && typeof opt.transform !== 'function') {
        throw error('transform option must be a function');
    }

    // Notify people of common mistakes...
    if (opt.read) {
        warn('There is no ' + magenta('`read`') + ' option. Did you mean to provide it for ' + magenta('`gulp.src`') + ' perhaps?');
    }

    // Defaults:
    opt.quiet = bool(opt, 'quiet', false);
    opt.ignorePath = toArray(opt.ignorePath).map(unixify);
    opt.relative = bool(opt, 'relative', false);
    opt.addRootSlash = bool(opt, 'addRootSlash', !opt.relative);
    opt.transform = defaults(opt, 'transform', transform);
    opt.tags = tags();
    opt.tags.name = defaults(opt, 'name', 'inject');
    transform.selfClosingTag = bool(opt, 'selfClosingTag', false);

    // Is the first parameter a Vinyl File Stream:
    if (typeof sources.on === 'function' && typeof sources.pipe === 'function') {
        return handleVinylStream(sources, opt);
    }

    throw error('passing target file as a string is deprecated! Pass a vinyl file stream (i.e. use `gulp.src`)!');
};

function defaults(options, prop, defaultValue) {
    return options[prop] || defaultValue;
}

function bool(options, prop, defaultVal) {
    return typeof options[prop] !== 'undefined' ? !!options[prop] : defaultVal;
}

/**
 * Handle injection when files to
 * inject comes from a Vinyl File Stream
 *
 * @param {Stream} sources
 * @param {Object} opt
 * @returns {Stream}
 */
function handleVinylStream(sources, opt) {
    var collected = collectFilesToInject(sources, opt);

    return es.map(function(target, cb) {
        if (target.isStream()) {
            return cb(error('Streams not supported for target templates!'));
        }
        collected(function(collection) {
            target.contents = getNewContent(target, collection, opt);
            cb(null, target);
        });
    });
}

/**
 * Collecting files to inject from Vinyl File Stream
 *
 * Returns an almost promise like function which can be
 * called multiple times with a callback, that will be
 * resolved with the result of the file collection.
 *
 * @param {Stream} sources
 * @param {Object} opt
 * @returns {Function}
 */
function collectFilesToInject(sources, opt) {
    var collection = [],
        done = false,
        queue = [];

    sources.pipe(es.through(collector(collection, opt), function() {
        done = true;
        while (queue.length) {
            resolve(queue.shift());
        }
    }));

    function resolve(cb) {
        setImmediate(function() {
            cb(collection);
        });
    }

    return function(cb) {
        if (done) {
            resolve(cb);
        } else {
            queue.push(cb);
        }
    };
}

/**
 * Create a file collecting function
 * to be used in es.through
 *
 * @param {Array} collection  Collection to fill with files
 * @param {Object} opt
 * @returns {Function}
 */
function collector(collection, opt) {
    return function(file) {
        if (!file.path) {
            return;
        }

        collection.push(file);
    };
}

/**
 * Get new content for template
 * with all injections made
 *
 * @param {Object} target
 * @param {Array} collection
 * @param {Object} opt
 * @returns {Buffer}
 */
function getNewContent(target, collection, opt) {
    var oldContent = target.contents;
    if (!collection.length) {
        if (!opt.quiet) {
            log('Nothing to inject into ' + magenta(target.relative) + '.');
        }
        return oldContent;
    }
    var tags = {};
    var targetExt = extname(target.path);

    var filesPerTags = groupBy(collection, function(file) {
        var ext = extname(file.path);
        var startTag = opt.tags.start(targetExt, ext, opt.starttag);
        var endTag = opt.tags.end(targetExt, ext, opt.endtag);
        var tag = startTag + endTag;
        if (!tags[tag]) {
            tags[tag] = {
                start: startTag,
                end: endTag
            };
        }
        return tag;
    });

    var startAndEndTags = Object.keys(filesPerTags);

    if (!opt.quiet) {
        log(cyan(collection.length) + ' files into ' + magenta(target.relative) + '.');
    }

    return new Buffer(startAndEndTags.reduce(function eachInCollection(contents, tag) {
        var files = filesPerTags[tag];
        var startTag = tags[tag].start;
        var endTag = tags[tag].end;

        return contents.replace(
            getInjectorTagsRegExp(startTag, endTag),
            function injector(match, starttag, indent, content, endtag) {
                var starttagArray = opt.removeTags ? [] : [starttag];
                var endtagArray = opt.removeTags ? [] : [endtag];
                return starttagArray
                    .concat(files.reduce(function transformFile(lines, file, i) {
                        var filepath = getFilepath(file, target, opt);
                        var transformedContents = opt.transform(filepath, file, i, files.length, target);
                        if (typeof transformedContents !== 'string') {
                            return lines;
                        }
                        return lines.concat(transformedContents);
                    }, []))
                    .concat(endtagArray)
                    .join(indent);
            }
        );
    }, String(oldContent)));
}

function getFilepath(sourceFile, targetFile, opt) {
    var base = opt.relative ? path.dirname(targetFile.path) : sourceFile.cwd;

    var filepath = unixify(path.relative(base, sourceFile.path));

    if (opt.ignorePath.length) {
        filepath = removeBasePath(opt.ignorePath, filepath);
    }

    if (opt.addPrefix) {
        filepath = addPrefix(filepath, opt.addPrefix);
    }

    if (opt.addRootSlash) {
        filepath = addRootSlash(filepath);
    } else if (!opt.addPrefix) {
        filepath = removeRootSlash(filepath);
    }

    if (opt.addSuffix) {
        filepath = addSuffix(filepath, opt.addSuffix);
    }

    return filepath;
}

function getTag(tag, ext) {
    return tag.replace('{{ext}}', ext);
}

function getInjectorTagsRegExp(starttag, endtag) {
    return new RegExp('(' + makeWhiteSpaceOptional(escapeForRegExp(starttag)) + ')(\\s*)(\\n|\\r|.)*?(' + makeWhiteSpaceOptional(escapeForRegExp(endtag)) + ')', 'gi');
}

function makeWhiteSpaceOptional(str) {
    return str.replace(/\s+/g, '\\s*');
}

function escapeForRegExp(str) {
    return str.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
}

function unixify(filepath) {
    return filepath.replace(/\\/g, '/');
}

function addRootSlash(filepath) {
    return filepath.replace(/^\/*([^\/])/, '/$1');
}

function removeRootSlash(filepath) {
    return filepath.replace(/^\/+/, '');
}

function addPrefix(filepath, prefix) {
    return prefix + filepath;
}

function addSuffix(filepath, suffix) {
    return filepath + suffix;
}

function removeBasePath(basedir, filepath) {
    return toArray(basedir).reduce(function(path, remove) {
        if (path[0] === '/' && remove[0] !== '/') {
            remove = '/' + remove;
        }
        if (path[0] !== '/' && remove[0] === '/') {
            path = '/' + path;
        }
        if (remove && path.indexOf(remove) === 0) {
            return path.slice(remove.length);
        }
        return path;
    }, filepath);
}

function toArray(arr) {
    if (!Array.isArray(arr)) {
        return arr ? [arr] : [];
    }
    return arr;
}

function groupBy(arr, cb) {
    var result = {};
    for (var i = 0; i < arr.length; i++) {
        var key = cb(arr[i]);
        if (!result[key]) {
            result[key] = [];
        }
        result[key].push(arr[i]);
    }
    return result;
}

function log(message) {
    gutil.log(magenta(PLUGIN_NAME), message);
}

function warn(message) {
    log(red('WARNING') + ' ' + message);
}

function error(message) {
    return new PluginError(PLUGIN_NAME, message);
}<|endoftext|>


<commit_before>'use strict';

import React from 'react';
import EventHandlersMixin from './mixins/event-handlers';
import HelpersMixin from './mixins/helpers';
import initialState from './initial-state';
import defaultProps from './default-props';
import createReactClass from 'create-react-class';
import classnames from 'classnames';
import assign from 'object-assign';

import { Track } from './track';
import { Dots } from './dots';
import { PrevArrow, NextArrow } from './arrows';

export var InnerSlider = createReactClass({
  mixins: [HelpersMixin, EventHandlersMixin],
  list: null,
  track: null,
  listRefHandler: function (ref) {
    this.list = ref;
  },
  trackRefHandler: function (ref) {
    this.track = ref;
  },
  getInitialState: function () {
    return Object.assign({}, initialState, {
      currentSlide: this.props.initialSlide
    });
  },
  getDefaultProps: function () {
    return defaultProps;
  },
  componentWillMount: function () {
    if (this.props.init) {
      this.props.init();
    }
    this.setState({
      mounted: true
    });
    var lazyLoadedList = [];
    for (var i = 0; i < React.Children.count(this.props.children); i++) {
      if (i >= this.state.currentSlide && i < this.state.currentSlide + this.props.slidesToShow) {
        lazyLoadedList.push(i);
      }
    }

    if (this.props.lazyLoad && this.state.lazyLoadedList.length === 0) {
      this.setState({
        lazyLoadedList: lazyLoadedList
      });
    }
  },
  componentDidMount: function componentDidMount() {
    // Hack for autoplay -- Inspect Later
    this.initialize(this.props);
    this.adaptHeight();

    // To support server-side rendering
    if (!window) {
      return
    }
    if (window.addEventListener) {
      window.addEventListener('resize', this.onWindowResized);
    } else {
      window.attachEvent('onresize', this.onWindowResized);
    }
  },
  componentWillUnmount: function componentWillUnmount() {
    if (this.animationEndCallback) {
      clearTimeout(this.animationEndCallback);
    }
    if (window.addEventListener) {
      window.removeEventListener('resize', this.onWindowResized);
    } else {
      window.detachEvent('onresize', this.onWindowResized);
    }
    if (this.state.autoPlayTimer) {
      clearInterval(this.state.autoPlayTimer);
    }
  },
  componentWillReceiveProps: function (nextProps) {
    if (this.props.slickGoTo != nextProps.slickGoTo) {
      if (process.env.NODE_ENV !== 'production') {
        console.warn('react-slick deprecation warning: slickGoTo prop is deprecated and it will be removed in next release. Use slickGoTo method instead')
      }
      this.changeSlide({
        message: 'index',
        index: nextProps.slickGoTo,
        currentSlide: this.state.currentSlide
      });
    } else if (this.state.currentSlide >= nextProps.children.length) {
      this.update(nextProps);
      this.changeSlide({
        message: 'index',
        index: nextProps.children.length - nextProps.slidesToShow,
        currentSlide: this.state.currentSlide
      });
    } else {
      this.update(nextProps);
    }
  },
  componentDidUpdate: function () {
    this.adaptHeight();
  },
  onWindowResized: function () {
    this.update(this.props);
    // animating state should be cleared while resizing, otherwise autoplay stops working
    this.setState({
      animating: false
    });
    clearTimeout(this.animationEndCallback);
    delete this.animationEndCallback;
  },
  slickPrev: function () {
    this.changeSlide({ message: 'previous' });
  },
  slickNext: function () {
    this.changeSlide({ message: 'next' });
  },
  slickGoTo: function (slide) {
    slide = Number(slide)
    !isNaN(slide) && this.changeSlide({
      message: 'index',
      index: slide,
      currentSlide: this.state.currentSlide
    });
  },
  render: function () {
    var className = classnames('slick-initialized', 'slick-slider', this.props.className, {
      'slick-vertical': this.props.vertical,
    });

    var trackProps = {
      fade: this.props.fade,
      cssEase: this.props.cssEase,
      speed: this.props.speed,
      infinite: this.props.infinite,
      centerMode: this.props.centerMode,
      focusOnSelect: this.props.focusOnSelect ? this.selectHandler : null,
      currentSlide: this.state.currentSlide,
      lazyLoad: this.props.lazyLoad,
      lazyLoadedList: this.state.lazyLoadedList,
      rtl: this.props.rtl,
      slideWidth: this.state.slideWidth,
      slidesToShow: this.props.slidesToShow,
      slidesToScroll: this.props.slidesToScroll,
      slideCount: this.state.slideCount,
      trackStyle: this.state.trackStyle,
      variableWidth: this.props.variableWidth
    };

    var dots;

    if (this.props.dots === true && this.state.slideCount >= this.props.slidesToShow) {
      var dotProps = {
        dotsClass: this.props.dotsClass,
        slideCount: this.state.slideCount,
        slidesToShow: this.props.slidesToShow,
        currentSlide: this.state.currentSlide,
        slidesToScroll: this.props.slidesToScroll,
        clickHandler: this.changeSlide,
        children: this.props.children,
        customPaging: this.props.customPaging
      };

      dots = (<Dots {...dotProps} />);
    }

    var prevArrow, nextArrow;

    var arrowProps = {
      infinite: this.props.infinite,
      centerMode: this.props.centerMode,
      currentSlide: this.state.currentSlide,
      slideCount: this.state.slideCount,
      slidesToShow: this.props.slidesToShow,
      prevArrow: this.props.prevArrow,
      nextArrow: this.props.nextArrow,
      clickHandler: this.changeSlide
    };

    if (this.props.arrows) {
      prevArrow = (<PrevArrow {...arrowProps} />);
      nextArrow = (<NextArrow {...arrowProps} />);
    }

    var verticalHeightStyle = null;

    if (this.props.vertical) {
      verticalHeightStyle = {
        height: this.state.listHeight,
      };
    }

    var centerPaddingStyle = null;

    if (this.props.vertical === false) {
      if (this.props.centerMode === true) {
        centerPaddingStyle = {
          padding: ('0px ' + this.props.centerPadding)
        };
      }
    } else {
      if (this.props.centerMode === true) {
        centerPaddingStyle = {
          padding: (this.props.centerPadding + ' 0px')
        };
      }
    }

    const listStyle = assign({}, verticalHeightStyle, centerPaddingStyle);

    return (
      <div
        className={className}
        onMouseEnter={this.onInnerSliderEnter}
        onMouseLeave={this.onInnerSliderLeave}
        onMouseOver={this.onInnerSliderOver}
      >
        {prevArrow}
        <div
          ref={this.listRefHandler}
          className="slick-list"
          style={listStyle}
          onMouseDown={this.swipeStart}
          onMouseMove={this.state.dragging ? this.swipeMove : null}
          onMouseUp={this.swipeEnd}
          onMouseLeave={this.state.dragging ? this.swipeEnd : null}
          onTouchStart={this.swipeStart}
          onTouchMove={this.state.dragging ? this.swipeMove : null}
          onTouchEnd={this.swipeEnd}
          onTouchCancel={this.state.dragging ? this.swipeEnd : null}
          onKeyDown={this.props.accessibility ? this.keyHandler : null}>
          <Track ref={this.trackRefHandler} {...trackProps}>
            {this.props.children}
          </Track>
        </div>
        {nextArrow}
        {dots}
      </div>
    );
  }
});
<commit_msg>fix when use lazyLoad in centerMode the elements on the left side can not be rendered<commit_after>'use strict';

import React from 'react';
import EventHandlersMixin from './mixins/event-handlers';
import HelpersMixin from './mixins/helpers';
import initialState from './initial-state';
import defaultProps from './default-props';
import createReactClass from 'create-react-class';
import classnames from 'classnames';
import assign from 'object-assign';

import { Track } from './track';
import { Dots } from './dots';
import { PrevArrow, NextArrow } from './arrows';

export var InnerSlider = createReactClass({
  mixins: [HelpersMixin, EventHandlersMixin],
  list: null,
  track: null,
  listRefHandler: function (ref) {
    this.list = ref;
  },
  trackRefHandler: function (ref) {
    this.track = ref;
  },
  getInitialState: function () {
    return Object.assign({}, initialState, {
      currentSlide: this.props.initialSlide
    });
  },
  getDefaultProps: function () {
    return defaultProps;
  },
  componentWillMount: function () {
    if (this.props.init) {
      this.props.init();
    }
    this.setState({
      mounted: true
    });
    var lazyLoadedList = [];
    const slidesToShow = this.props.slidesToShow;
    const childrenLen = React.Children.count(this.props.children);
    const currentSlide = this.state.currentSlide;
    for (var i = 0; i < childrenLen; i++) {
      if (i >= currentSlide && i < currentSlide + slidesToShow) {
        lazyLoadedList.push(i);
      }
    }
    if (this.props.centerMode === true) {
      let additionalCount = Math.floor(slidesToShow / 2);
      if (parseInt(this.props.centerPadding) > 0) {
        additionalCount += 1;
      }
      let additionalNum = currentSlide;
      for (let j = 0; j < additionalCount; j++) {
        additionalNum = additionalNum - 1;
        if (additionalNum < 0) {
          additionalNum = childrenLen - 1;
        }
        lazyLoadedList.push(additionalNum);
      }
    }

    if (this.props.lazyLoad && this.state.lazyLoadedList.length === 0) {
      this.setState({
        lazyLoadedList: lazyLoadedList
      });
    }
  },
  componentDidMount: function componentDidMount() {
    // Hack for autoplay -- Inspect Later
    this.initialize(this.props);
    this.adaptHeight();

    // To support server-side rendering
    if (!window) {
      return
    }
    if (window.addEventListener) {
      window.addEventListener('resize', this.onWindowResized);
    } else {
      window.attachEvent('onresize', this.onWindowResized);
    }
  },
  componentWillUnmount: function componentWillUnmount() {
    if (this.animationEndCallback) {
      clearTimeout(this.animationEndCallback);
    }
    if (window.addEventListener) {
      window.removeEventListener('resize', this.onWindowResized);
    } else {
      window.detachEvent('onresize', this.onWindowResized);
    }
    if (this.state.autoPlayTimer) {
      clearInterval(this.state.autoPlayTimer);
    }
  },
  componentWillReceiveProps: function (nextProps) {
    if (this.props.slickGoTo != nextProps.slickGoTo) {
      if (process.env.NODE_ENV !== 'production') {
        console.warn('react-slick deprecation warning: slickGoTo prop is deprecated and it will be removed in next release. Use slickGoTo method instead')
      }
      this.changeSlide({
        message: 'index',
        index: nextProps.slickGoTo,
        currentSlide: this.state.currentSlide
      });
    } else if (this.state.currentSlide >= nextProps.children.length) {
      this.update(nextProps);
      this.changeSlide({
        message: 'index',
        index: nextProps.children.length - nextProps.slidesToShow,
        currentSlide: this.state.currentSlide
      });
    } else {
      this.update(nextProps);
    }
  },
  componentDidUpdate: function () {
    this.adaptHeight();
  },
  onWindowResized: function () {
    this.update(this.props);
    // animating state should be cleared while resizing, otherwise autoplay stops working
    this.setState({
      animating: false
    });
    clearTimeout(this.animationEndCallback);
    delete this.animationEndCallback;
  },
  slickPrev: function () {
    this.changeSlide({ message: 'previous' });
  },
  slickNext: function () {
    this.changeSlide({ message: 'next' });
  },
  slickGoTo: function (slide) {
    slide = Number(slide)
    !isNaN(slide) && this.changeSlide({
      message: 'index',
      index: slide,
      currentSlide: this.state.currentSlide
    });
  },
  render: function () {
    var className = classnames('slick-initialized', 'slick-slider', this.props.className, {
      'slick-vertical': this.props.vertical,
    });

    var trackProps = {
      fade: this.props.fade,
      cssEase: this.props.cssEase,
      speed: this.props.speed,
      infinite: this.props.infinite,
      centerMode: this.props.centerMode,
      focusOnSelect: this.props.focusOnSelect ? this.selectHandler : null,
      currentSlide: this.state.currentSlide,
      lazyLoad: this.props.lazyLoad,
      lazyLoadedList: this.state.lazyLoadedList,
      rtl: this.props.rtl,
      slideWidth: this.state.slideWidth,
      slidesToShow: this.props.slidesToShow,
      slidesToScroll: this.props.slidesToScroll,
      slideCount: this.state.slideCount,
      trackStyle: this.state.trackStyle,
      variableWidth: this.props.variableWidth
    };

    var dots;

    if (this.props.dots === true && this.state.slideCount >= this.props.slidesToShow) {
      var dotProps = {
        dotsClass: this.props.dotsClass,
        slideCount: this.state.slideCount,
        slidesToShow: this.props.slidesToShow,
        currentSlide: this.state.currentSlide,
        slidesToScroll: this.props.slidesToScroll,
        clickHandler: this.changeSlide,
        children: this.props.children,
        customPaging: this.props.customPaging
      };

      dots = (<Dots {...dotProps} />);
    }

    var prevArrow, nextArrow;

    var arrowProps = {
      infinite: this.props.infinite,
      centerMode: this.props.centerMode,
      currentSlide: this.state.currentSlide,
      slideCount: this.state.slideCount,
      slidesToShow: this.props.slidesToShow,
      prevArrow: this.props.prevArrow,
      nextArrow: this.props.nextArrow,
      clickHandler: this.changeSlide
    };

    if (this.props.arrows) {
      prevArrow = (<PrevArrow {...arrowProps} />);
      nextArrow = (<NextArrow {...arrowProps} />);
    }

    var verticalHeightStyle = null;

    if (this.props.vertical) {
      verticalHeightStyle = {
        height: this.state.listHeight,
      };
    }

    var centerPaddingStyle = null;

    if (this.props.vertical === false) {
      if (this.props.centerMode === true) {
        centerPaddingStyle = {
          padding: ('0px ' + this.props.centerPadding)
        };
      }
    } else {
      if (this.props.centerMode === true) {
        centerPaddingStyle = {
          padding: (this.props.centerPadding + ' 0px')
        };
      }
    }

    const listStyle = assign({}, verticalHeightStyle, centerPaddingStyle);

    return (
      <div
        className={className}
        onMouseEnter={this.onInnerSliderEnter}
        onMouseLeave={this.onInnerSliderLeave}
        onMouseOver={this.onInnerSliderOver}
      >
        {prevArrow}
        <div
          ref={this.listRefHandler}
          className="slick-list"
          style={listStyle}
          onMouseDown={this.swipeStart}
          onMouseMove={this.state.dragging ? this.swipeMove : null}
          onMouseUp={this.swipeEnd}
          onMouseLeave={this.state.dragging ? this.swipeEnd : null}
          onTouchStart={this.swipeStart}
          onTouchMove={this.state.dragging ? this.swipeMove : null}
          onTouchEnd={this.swipeEnd}
          onTouchCancel={this.state.dragging ? this.swipeEnd : null}
          onKeyDown={this.props.accessibility ? this.keyHandler : null}>
          <Track ref={this.trackRefHandler} {...trackProps}>
            {this.props.children}
          </Track>
        </div>
        {nextArrow}
        {dots}
      </div>
    );
  }
});
<|endoftext|>


<commit_before>import "mocha"
import * as assert from "assert"
import { model } from "../js/components/Filter.js"
import xs from "xstream"
import fromDiagram from "xstream/extra/fromDiagram"

describe("defaultReducer", function () {
  it("Generates a state if none exists", () => {
    const state = null
    const reducers$ = model(
      xs.empty(),
      xs.empty(),
      xs.empty(),
      xs.empty(),
      xs.empty(),
      xs.empty()
    )

    reducers$.addListener({
      next(f) {
        const newState = f(state)
        assert.deepStrictEqual(newState.core.output, {})
        assert.deepStrictEqual(newState.core.filter_output, {})
        assert.deepStrictEqual(newState.core.state, {dose: false, cell: false, trtType: false})
      },
      error(e) {
        done(e)
      },
      complete() {},
    })
  })
  it("Generates a clean state if one exists", () => {
    const state = {
      core: ["bla", "blah"],
      settings: { old: true },
    }
    const reducers$ = model(
      xs.empty(),
      xs.empty(),
      xs.empty(),
      xs.empty(),
      xs.empty(),
      xs.empty()
    )

    reducers$.addListener({
      next(f) {
        const newState = f(state)
        assert.deepStrictEqual(newState.core.output, {})
      },
      error(e) {
        done(e)
      },
      complete() {},
    })
  })
})

describe("possibleValuesReducer", function () {
  it("Updates the state with the possible values, leaves the rest intact", () => {
    const state = {
      core: { entry: "test", dirty: false },
      settings: {},
    }

    const possibleValues = [
      { filter1: [1, 2, 3] },
      { filter2: ["a", "b", "c"] },
    ]

    const possibleValues$ = xs.of(possibleValues)

    const reducers$ = model(
      possibleValues$,
      xs.empty(),
      xs.empty(),
      xs.empty(),
      xs.empty(),
      xs.empty()
    )

    reducers$
      .drop(1) // avoid the defaultReducer
      .addListener({
        next(f) {
          const newState = f(state)
          assert.deepStrictEqual(newState.core, state.core)
        },
        error(e) {
          done(e)
        },
        complete() {},
      })
  })
})

describe("inputReducer", function () {
  it("Updates the state when a new 'input' (signature) is provided", () => {
    const state = {
      core: { input: ["a", "b"] },
      settings: {},
    }

    const possibleValues = [
      { filter1: [1, 2, 3] },
      { filter2: ["a", "b", "c"] },
    ]

    const newInput = ["c", "d"]

    const input$ = xs.of(newInput)

    const reducers$ = model(
      xs.empty(),
      input$,
      xs.empty(),
      xs.empty(),
      xs.empty(),
      xs.empty()
    )

    reducers$
      .drop(1) // avoid the defaultReducer
      .addListener({
        next(f) {
          const newState = f(state)
          assert.deepStrictEqual(newState.core.input, newInput)
        },
        error(e) {
          done(e)
        },
        complete() {},
      })
  })
})

describe("toggleReducer with and without modifier", function () {
  it("Updates the state when a filter value is clicked", () => {
    const possibleValues = {
      dose: [1, 2, 3],
      cell: ["cell1", "cell2", "cell3"],
      trtType: ["a", "b", "c"],
    }
    const possibleValues$ = fromDiagram(`-x`).mapTo(possibleValues)

    const newInput = ["c", "d"]
    const input$ = fromDiagram("-x").mapTo(newInput)

    const switchDose1 = { dose: 1 }
    const switchCell = {cell: "cell1"}

    // Result of clicking on dose = 1
    const switchDose1$ = fromDiagram("--x").mapTo(switchDose1)
    // Result of clicking on cell = cell1
    const action2$ = fromDiagram("---x").mapTo(switchCell)
    // Result of clicking on dose = 1 again, this adds the filter value again
    const action3$ = fromDiagram("----x").mapTo(switchDose1)
    // Result of clicking on dose = 1 again, this time with modifier (see modifier$)
    const action4$ = fromDiagram("-------x").mapTo(switchDose1)
    const filterValuesAction$ = xs.merge(
      switchDose1$,
      action2$,
      action3$,
      action4$
    )

    const modifierFalse$ = fromDiagram("x------").mapTo(false)
    const modifierTrue$  = fromDiagram("------x").mapTo(true)
    const modifier$ = xs.merge(modifierFalse$, modifierTrue$)

    const reducers$ = model(
      possibleValues$,
      input$,
      filterValuesAction$,
      modifier$,
      xs.empty(),
      xs.empty()
    )

    // Predefine filters in settings as possible filters will be updated there
    const defaultFilter = {settings: {filter: {}}}
    const state$ = reducers$.fold((state, reducer) => reducer(state), defaultFilter)

    // The reducers should generate the following sequence for state.core.output
    // Values are duplicated as output & dirty reducers are changing
    let expectedOutput = [
      {},
      {},
      {},
      { dose: [2, 3] },
      { dose: [2, 3] },
      { dose: [2, 3], cell: ["cell2", "cell3"] },
      { dose: [2, 3], cell: ["cell2", "cell3"] },
      { dose: [1, 2, 3], cell: ["cell2", "cell3"] },
      { dose: [1, 2, 3], cell: ["cell2", "cell3"] },
      { dose: [], cell: ["cell2", "cell3"] },
      { dose: [], cell: ["cell2", "cell3"] },
    ]

    state$
      .drop(1) // drop the first state as it is undefined
      .addListener({
        next(state) {
          assert.deepStrictEqual(state?.core?.output, expectedOutput.shift())
        },
        error(e) {
          console.log(e)
        },
        complete() {
          console.log("done!")
        },
      })
  })
})

describe("uiReducer", function () {
  it("Becomes dirty when a filter value is clicked and becomes clean when the filter is clicked again", () => {
    const possibleValues = {
      dose: [1, 2, 3],
      cell: ["cell1", "cell2", "cell3"],
      trtType: ["a", "b", "c"],
    }
    const possibleValues$ = fromDiagram(`-x`).mapTo(possibleValues)

    const newInput = ["c", "d"]
    const input$ = fromDiagram("-x").mapTo(newInput)

    const switchDose1 = { dose: 1 }

    // Result of clicking on dose = 1
    const switchDose1$ = fromDiagram("---x").mapTo(switchDose1)
    // Result of clicking on dose = 1 again, this adds the filter value again
    const action1$ = fromDiagram("----x").mapTo(switchDose1)
    const action2$ = fromDiagram("-----x").mapTo(switchDose1)
    const filterValuesAction$ = xs.merge(
      switchDose1$,
      action1$,
      action2$
    )

    const modifierFalse$ = xs.of(false)

    const openFilter = {
      dose: true,
      cell: false,
      trtType: false,
    }
    const openFilter$ = fromDiagram("--x").mapTo(openFilter)

    const reducers$ = model(
      possibleValues$,
      input$,
      filterValuesAction$,
      modifierFalse$,
      openFilter$,
      xs.empty()
    )

    // Predefine filters in settings as possible filters will be updated there
    const defaultFilter = {settings: {filter: {}}}
    const state$ = reducers$.fold((state, reducer) => reducer(state), defaultFilter)

    // The reducers should generate the following sequence for state.core.dirty
    // Values are duplicated as output & dirty reducers are changing
    let expectedOutput = [
      false,
      false,
      false,
      false,
      false,
      false,
      true,
      true,
      false,
      false,
      true,
      true,
    ]

    state$
      .drop(1) // drop the first state as it is undefined
      .addListener({
        next(state) {
          assert.deepStrictEqual(state?.core?.dirty, expectedOutput.shift())
        },
        error(e) {
          console.log(e)
        },
        complete() {
          console.log("done!")
        },
      })
  })
})
<commit_msg>Add unit test for search queries in filters<commit_after>import "mocha"
import * as assert from "assert"
import { model } from "../js/components/Filter.js"
import xs from "xstream"
import fromDiagram from "xstream/extra/fromDiagram"

describe("defaultReducer", function () {
  it("Generates a state if none exists", () => {
    const state = null
    const reducers$ = model(
      xs.empty(),
      xs.empty(),
      xs.empty(),
      xs.empty(),
      xs.empty(),
      xs.empty()
    )

    reducers$.addListener({
      next(f) {
        const newState = f(state)
        assert.deepStrictEqual(newState.core.output, {})
        assert.deepStrictEqual(newState.core.filter_output, {})
        assert.deepStrictEqual(newState.core.state, {dose: false, cell: false, trtType: false})
      },
      error(e) {
        done(e)
      },
      complete() {},
    })
  })
  it("Generates a clean state if one exists", () => {
    const state = {
      core: ["bla", "blah"],
      settings: { old: true },
    }
    const reducers$ = model(
      xs.empty(),
      xs.empty(),
      xs.empty(),
      xs.empty(),
      xs.empty(),
      xs.empty()
    )

    reducers$.addListener({
      next(f) {
        const newState = f(state)
        assert.deepStrictEqual(newState.core.output, {})
      },
      error(e) {
        done(e)
      },
      complete() {},
    })
  })
})

describe("possibleValuesReducer", function () {
  it("Updates the state with the possible values, leaves the rest intact", () => {
    const state = {
      core: { entry: "test", dirty: false },
      settings: {},
    }

    const possibleValues = [
      { filter1: [1, 2, 3] },
      { filter2: ["a", "b", "c"] },
    ]

    const possibleValues$ = xs.of(possibleValues)

    const reducers$ = model(
      possibleValues$,
      xs.empty(),
      xs.empty(),
      xs.empty(),
      xs.empty(),
      xs.empty()
    )

    reducers$
      .drop(1) // avoid the defaultReducer
      .addListener({
        next(f) {
          const newState = f(state)
          assert.deepStrictEqual(newState.core, state.core)
        },
        error(e) {
          done(e)
        },
        complete() {},
      })
  })
})

describe("inputReducer", function () {
  it("Updates the state when a new 'input' (signature) is provided", () => {
    const state = {
      core: { input: ["a", "b"] },
      settings: {},
    }

    const possibleValues = [
      { filter1: [1, 2, 3] },
      { filter2: ["a", "b", "c"] },
    ]

    const newInput = ["c", "d"]

    const input$ = xs.of(newInput)

    const reducers$ = model(
      xs.empty(),
      input$,
      xs.empty(),
      xs.empty(),
      xs.empty(),
      xs.empty()
    )

    reducers$
      .drop(1) // avoid the defaultReducer
      .addListener({
        next(f) {
          const newState = f(state)
          assert.deepStrictEqual(newState.core.input, newInput)
        },
        error(e) {
          done(e)
        },
        complete() {},
      })
  })
})

describe("toggleReducer with and without modifier", function () {
  it("Updates the state when a filter value is clicked", () => {
    const possibleValues = {
      dose: [1, 2, 3],
      cell: ["cell1", "cell2", "cell3"],
      trtType: ["a", "b", "c"],
    }
    const possibleValues$ = fromDiagram(`-x`).mapTo(possibleValues)

    const newInput = ["c", "d"]
    const input$ = fromDiagram("-x").mapTo(newInput)

    const switchDose1 = { dose: 1 }
    const switchCell = {cell: "cell1"}

    // Result of clicking on dose = 1
    const switchDose1$ = fromDiagram("--x").mapTo(switchDose1)
    // Result of clicking on cell = cell1
    const action2$ = fromDiagram("---x").mapTo(switchCell)
    // Result of clicking on dose = 1 again, this adds the filter value again
    const action3$ = fromDiagram("----x").mapTo(switchDose1)
    // Result of clicking on dose = 1 again, this time with modifier (see modifier$)
    const action4$ = fromDiagram("-------x").mapTo(switchDose1)
    const filterValuesAction$ = xs.merge(
      switchDose1$,
      action2$,
      action3$,
      action4$
    )

    const modifierFalse$ = fromDiagram("x------").mapTo(false)
    const modifierTrue$  = fromDiagram("------x").mapTo(true)
    const modifier$ = xs.merge(modifierFalse$, modifierTrue$)

    const reducers$ = model(
      possibleValues$,
      input$,
      filterValuesAction$,
      modifier$,
      xs.empty(),
      xs.empty()
    )

    // Predefine filters in settings as possible filters will be updated there
    const defaultFilter = {settings: {filter: {}}}
    const state$ = reducers$.fold((state, reducer) => reducer(state), defaultFilter)

    // The reducers should generate the following sequence for state.core.output
    // Values are duplicated as output & dirty reducers are changing
    let expectedOutput = [
      {},
      {},
      {},
      { dose: [2, 3] },
      { dose: [2, 3] },
      { dose: [2, 3], cell: ["cell2", "cell3"] },
      { dose: [2, 3], cell: ["cell2", "cell3"] },
      { dose: [1, 2, 3], cell: ["cell2", "cell3"] },
      { dose: [1, 2, 3], cell: ["cell2", "cell3"] },
      { dose: [], cell: ["cell2", "cell3"] },
      { dose: [], cell: ["cell2", "cell3"] },
    ]

    state$
      .drop(1) // drop the first state as it is undefined
      .addListener({
        next(state) {
          assert.deepStrictEqual(state?.core?.output, expectedOutput.shift())
        },
        error(e) {
          console.log(e)
        },
        complete() {
          console.log("done!")
        },
      })
  })
})

describe("uiReducer", function () {
  it("Becomes dirty when a filter value is clicked and becomes clean when the filter is clicked again", () => {
    const possibleValues = {
      dose: [1, 2, 3],
      cell: ["cell1", "cell2", "cell3"],
      trtType: ["a", "b", "c"],
    }
    const possibleValues$ = fromDiagram(`-x`).mapTo(possibleValues)

    const newInput = ["c", "d"]
    const input$ = fromDiagram("-x").mapTo(newInput)

    const switchDose1 = { dose: 1 }

    // Result of clicking on dose = 1
    const switchDose1$ = fromDiagram("---x").mapTo(switchDose1)
    // Result of clicking on dose = 1 again, this adds the filter value again
    const action1$ = fromDiagram("----x").mapTo(switchDose1)
    const action2$ = fromDiagram("-----x").mapTo(switchDose1)
    const filterValuesAction$ = xs.merge(
      switchDose1$,
      action1$,
      action2$
    )

    const modifierFalse$ = xs.of(false)

    const openFilter = {
      dose: true,
      cell: false,
      trtType: false,
    }
    const openFilter$ = fromDiagram("--x").mapTo(openFilter)

    const reducers$ = model(
      possibleValues$,
      input$,
      filterValuesAction$,
      modifierFalse$,
      openFilter$,
      xs.empty()
    )

    // Predefine filters in settings as possible filters will be updated there
    const defaultFilter = {settings: {filter: {}}}
    const state$ = reducers$.fold((state, reducer) => reducer(state), defaultFilter)

    // The reducers should generate the following sequence for state.core.dirty
    // Values are duplicated as output & dirty reducers are changing
    let expectedOutput = [
      false,
      false,
      false,
      false,
      false,
      false,
      true,
      true,
      false,
      false,
      true,
      true,
    ]

    state$
      .drop(1) // drop the first state as it is undefined
      .addListener({
        next(state) {
          assert.deepStrictEqual(state?.core?.dirty, expectedOutput.shift())
        },
        error(e) {
          console.log(e)
        },
        complete() {
          console.log("done!")
        },
      })
  })
})

describe("Search query", function () {
  it("Updates the output which should match that of the search query, except for invalid values which should be discarded", () => {
    const possibleValues = {
      dose: [1, 2, 3],
      cell: ["cell1", "cell2", "cell3"],
      trtType: ["a", "b", "c"],
    }
    const possibleValues$ = fromDiagram("-x").mapTo(possibleValues)
    const search = xs.of({
      dose: [1, 2],
      cell: ["cell1", "cell2", "cell3"],
      trtType: ["a", "b", "d"]
    })
    const search$ = fromDiagram("--x").mapTo(search)

    const reducers$ = model(
      possibleValues$,
      xs.empty(),
      xs.empty(),
      xs.empty(),
      xs.empty(),
      search$
    )

    // Predefine filters in settings as possible filters will be updated there
    const defaultFilter = {settings: {filter: {}}}
    const state$ = reducers$.fold((state, reducer) => reducer(state), defaultFilter)

    // Outputs all valid values
    let expectedOutput = [
      {},
      {},
      {},
      { dose: [2, 3], cell: ["cell1", "cell2", "cell3"], trtType: ["a", "b"] },
    ]

    let expectedFilterOutput = [
      {},
      {},
      {},
      { dose: [2, 3], cell: undefined, trtType: ["a", "b"] },
    ]

    state$
      .drop(1) // drop the first state as it is undefined
      .addListener({
        next(state) {
          assert.deepStrictEqual(state?.core?.output, expectedOutput.shift())
          assert.deepStrictEqual(state?.core?.filter_output, expectedFilterOutput.shift())
        },
        error(e) {
          console.log(e)
        },
        complete() {
          console.log("done!")
        },
      })
  })
})
<|endoftext|>


<commit_before>'use strict'
import test from 'tape'
import Entry from '../entry'
import moment from 'moment'

test('simple entry construction 8am-10pm', t => {
  const e = new Entry('8am-10am worked on things')
  const {from, to} = e.getDates()
  t.ok(e.hasDates, 'entry has dates')
  t.equals(e.message, '8am-10am worked on things')
  t.equals(from.getHours(), 8, 'from is 8am')
  t.equals(to.getHours(), 10, 'from is 10am')

  t.end()
})

test('simple entry construction 8am-5pm', t => {
  const e = new Entry('8am-5pm worked on things')
  const {from, to} = e.getDates()
  t.ok(e.hasDates, 'entry has dates')
  t.equals(e.message, '8am-5pm worked on things')
  t.equals(from.getHours(), 8, 'from is 8am')
  t.equals(to.getHours(), 17, 'to is 5pm')

  t.end()
})

test('no times specified does not generate dates', t => {
  const e = new Entry('worked on some things')
  const {from, to} = e.getDates()
  t.notOk(e.hasDates, 'flag indicates that no dates are present')
  t.notOk(from, 'no from date provided')
  t.notOk(to, 'no to date provided')

  t.end()
})

test('passing date influences the dates', t => {
  const date = new Date('Jan 25, 2015 0:00:00')
  const msg = '8am-5pm worked on some things'
  const e = new Entry(msg, {date})
  const {from, to} = e.getDates()
  t.equals(from.getFullYear(), 2015, 'from has the same year')
  t.equals(from.getMonth(), 0, 'from has same month')
  t.equals(from.getDate(), 25, 'from has same day')
  t.equals(to.getFullYear(), 2015, 'to has the same year')
  t.equals(to.getMonth(), 0, 'to has the same month')
  t.equals(to.getDate(), 25, 'to has the same day')

  t.end()
})

test('no date defaults to today', t => {
  const date = new Date()
  const msg = '8am-5pm worked on some things'
  const e = new Entry(msg)
  const {from,to} = e.getDates()

  t.equals(from.getFullYear(), date.getFullYear(), 'from year is today')
  t.equals(from.getMonth(), date.getMonth(), 'from month is today')
  t.equals(from.getDate(), date.getDate(), 'from date is today')
  t.equals(to.getFullYear(), date.getFullYear(), 'to year is today')
  t.equals(to.getMonth(), date.getMonth(), 'to month is today')
  t.equals(to.getDate(), date.getDate(), 'to date is today')

  t.end()
})

test('duration set for 8am-10am', t => {
  const e = new Entry('8am-10am worked on some things')

  t.ok(e.duration, 'duration is present')
  t.equals(e.duration.hour, 2, '8am-10am is 2 hours')
  t.equals(e.duration.minute, 0, '8am-10am has 0 minutes')

  t.end()
})

test('duration set for 8am-1:30pm', t => {
  const e = new Entry('8am-1:30pm worked on some things')

  t.equals(e.duration.hour, 5, '8am-1:30pm is 5 hours')
  t.equals(e.duration.minute, 30, '8am-1:30pm trails 30 min')

  t.end()
})

test('duration set for 11pm-2am', t => {
  const e = new Entry('11pm-2am worked on some things')

  t.equals(e.duration.hour, 3, '11pm-2am is 3 hours')

  t.end()
})

test('constructor assigns _id', t => {
  const e = new Entry('8am-10am worked on some things')

  t.ok(e._id, '_id is ok')
  t.end()
})

test('parse #tags', t => {
  const e = new Entry('8-10am worked on things #tag1 #tag2')

  t.deepEqual(e.tags, ['#tag1', '#tag2'], 'tags are parsed out to an array')
  t.end()
})

test('parse unique #tags', t => {
  const e = new Entry('8-10am worked on things #tag1 #tag1')

  t.deepEqual(e.tags, ['#tag1'], 'tags only appear once')
  t.end()
})

test('toJSON() returns a json obj', t => {
  const e = new Entry('8am-10am worked on some things')

  const json = e.toJSON()
  t.equals(json.message, '8am-10am worked on some things', 'message')
  t.ok(json.hasDates, 'hasDates')
  t.equals(e.from, json.from, 'from')
  t.equals(e.to, json.to, 'to')
  t.equals(json.duration.seconds, 2*60*60, 'duration seconds')
  t.equals(json.duration.from, e.from, 'duration from')
  t.equals(json.duration.to, e.to, 'duration to')
  t.equals(json._id, e._id, '_id')
  t.ok(moment(json.toArr).isSame(json.to), 'to and toArr are same date')
  t.ok(moment(json.fromArr).isSame(json.from), 'from and fromArr are same date')
  t.ok(json.toArr instanceof Array, 'toArr is an array')
  t.ok(json.fromArr instanceof Array, 'fromArr is an array')

  t.end()
})

test('fromJSON() will create an Entry from existing document', t => {
  const date = new Date('Jan 25, 2015 0:00:00')
  const existing = new Entry('8am-10am worked on things', {date})
  const json = existing.toJSON()
  const e = Entry.fromJSON(json)

  t.equals(existing._id, e._id, '_id matches')
  t.equals(existing.message, e.message, 'message matches')
  t.equals(moment(existing.to).toString(), moment(e.to).toString(), 'to matches')
  t.equals(moment(existing.from).toString(), moment(e.from).toString(), 'from matches')

  t.end()

})

<commit_msg>fail toJSON/fromJSON supports #tags<commit_after>'use strict'
import test from 'tape'
import Entry from '../entry'
import moment from 'moment'

test('simple entry construction 8am-10pm', t => {
  const e = new Entry('8am-10am worked on things')
  const {from, to} = e.getDates()
  t.ok(e.hasDates, 'entry has dates')
  t.equals(e.message, '8am-10am worked on things')
  t.equals(from.getHours(), 8, 'from is 8am')
  t.equals(to.getHours(), 10, 'from is 10am')

  t.end()
})

test('simple entry construction 8am-5pm', t => {
  const e = new Entry('8am-5pm worked on things')
  const {from, to} = e.getDates()
  t.ok(e.hasDates, 'entry has dates')
  t.equals(e.message, '8am-5pm worked on things')
  t.equals(from.getHours(), 8, 'from is 8am')
  t.equals(to.getHours(), 17, 'to is 5pm')

  t.end()
})

test('no times specified does not generate dates', t => {
  const e = new Entry('worked on some things')
  const {from, to} = e.getDates()
  t.notOk(e.hasDates, 'flag indicates that no dates are present')
  t.notOk(from, 'no from date provided')
  t.notOk(to, 'no to date provided')

  t.end()
})

test('passing date influences the dates', t => {
  const date = new Date('Jan 25, 2015 0:00:00')
  const msg = '8am-5pm worked on some things'
  const e = new Entry(msg, {date})
  const {from, to} = e.getDates()
  t.equals(from.getFullYear(), 2015, 'from has the same year')
  t.equals(from.getMonth(), 0, 'from has same month')
  t.equals(from.getDate(), 25, 'from has same day')
  t.equals(to.getFullYear(), 2015, 'to has the same year')
  t.equals(to.getMonth(), 0, 'to has the same month')
  t.equals(to.getDate(), 25, 'to has the same day')

  t.end()
})

test('no date defaults to today', t => {
  const date = new Date()
  const msg = '8am-5pm worked on some things'
  const e = new Entry(msg)
  const {from,to} = e.getDates()

  t.equals(from.getFullYear(), date.getFullYear(), 'from year is today')
  t.equals(from.getMonth(), date.getMonth(), 'from month is today')
  t.equals(from.getDate(), date.getDate(), 'from date is today')
  t.equals(to.getFullYear(), date.getFullYear(), 'to year is today')
  t.equals(to.getMonth(), date.getMonth(), 'to month is today')
  t.equals(to.getDate(), date.getDate(), 'to date is today')

  t.end()
})

test('duration set for 8am-10am', t => {
  const e = new Entry('8am-10am worked on some things')

  t.ok(e.duration, 'duration is present')
  t.equals(e.duration.hour, 2, '8am-10am is 2 hours')
  t.equals(e.duration.minute, 0, '8am-10am has 0 minutes')

  t.end()
})

test('duration set for 8am-1:30pm', t => {
  const e = new Entry('8am-1:30pm worked on some things')

  t.equals(e.duration.hour, 5, '8am-1:30pm is 5 hours')
  t.equals(e.duration.minute, 30, '8am-1:30pm trails 30 min')

  t.end()
})

test('duration set for 11pm-2am', t => {
  const e = new Entry('11pm-2am worked on some things')

  t.equals(e.duration.hour, 3, '11pm-2am is 3 hours')

  t.end()
})

test('constructor assigns _id', t => {
  const e = new Entry('8am-10am worked on some things')

  t.ok(e._id, '_id is ok')
  t.end()
})

test('parse #tags', t => {
  const e = new Entry('8-10am worked on things #tag1 #tag2')

  t.deepEqual(e.tags, ['#tag1', '#tag2'], 'tags are parsed out to an array')
  t.end()
})

test('parse unique #tags', t => {
  const e = new Entry('8-10am worked on things #tag1 #tag1')

  t.deepEqual(e.tags, ['#tag1'], 'tags only appear once')
  t.end()
})

test('toJSON() returns a json obj', t => {
  const e = new Entry('8am-10am worked on some things #tag1 #tag2')

  const json = e.toJSON()
  t.equals(json.message, '8am-10am worked on some things #tag1 #tag2', 'message')
  t.ok(json.hasDates, 'hasDates')
  t.equals(e.from, json.from, 'from')
  t.equals(e.to, json.to, 'to')
  t.equals(json.duration.seconds, 2*60*60, 'duration seconds')
  t.equals(json.duration.from, e.from, 'duration from')
  t.equals(json.duration.to, e.to, 'duration to')
  t.equals(json._id, e._id, '_id')
  t.deepEquals(json.tags, e.tags, 'tags array')
  t.ok(moment(json.toArr).isSame(json.to), 'to and toArr are same date')
  t.ok(moment(json.fromArr).isSame(json.from), 'from and fromArr are same date')
  t.ok(json.toArr instanceof Array, 'toArr is an array')
  t.ok(json.fromArr instanceof Array, 'fromArr is an array')

  t.end()
})

test('fromJSON() will create an Entry from existing document', t => {
  const date = new Date('Jan 25, 2015 0:00:00')
  const existing = new Entry('8am-10am worked on things #tag1 #tag2', {date})
  const json = existing.toJSON()
  const e = Entry.fromJSON(json)

  t.equals(existing._id, e._id, '_id matches')
  t.equals(existing.message, e.message, 'message matches')
  t.deepEquals(existing.tags, e.tags, 'tags match')
  t.equals(e.tags.length, 2, '2 tags')
  t.equals(moment(existing.to).toString(), moment(e.to).toString(), 'to matches')
  t.equals(moment(existing.from).toString(), moment(e.from).toString(), 'from matches')

  t.end()

})

<|endoftext|>


<commit_before>'use strict';

// Load environment vars.
require('dotenv').config();

const cluster = require('cluster');

const config = require('./config');
const logger = require('./lib/logger');
const server = require('./server');

/**
 * exitHandler - When any of the workers die the cluster module will emit the 'exit' event.
 *
 * @see https://nodejs.org/docs/latest-v8.x/api/cluster.html#cluster_event_exit_1
 * @param {cluster.Worker} worker
 * @param {Number} code
 * @param {String} signal
 */
function exitHandler(worker, code, signal) {
  logger.debug(`Worker ${worker.process.pid} died.\n\nReason: (${signal || code}).\n\nrestarting...`);
  // Respawn a worker when it dies
  cluster.fork();
}

if (cluster.isMaster) {
  // Fork workers
  for (let workers = 0; workers < config.workers; workers++) { // eslint-disable-line
    cluster.fork();
  }
  // Register exit handler
  cluster.on('exit', exitHandler);
  logger.debug('Master process');
} else {
  server();
}
<commit_msg>Move importing of the server module inside the scope of the worker process. This change saves us unnecessary redis connections to the master process<commit_after>'use strict';

/* eslint-disable global-require */
// Load environment vars.
require('dotenv').config();

const cluster = require('cluster');

const config = require('./config');
const logger = require('./lib/logger');

/**
 * exitHandler - When any of the workers die the cluster module will emit the 'exit' event.
 *
 * @see https://nodejs.org/docs/latest-v8.x/api/cluster.html#cluster_event_exit_1
 * @param {cluster.Worker} worker
 * @param {Number} code
 * @param {String} signal
 */
function exitHandler(worker, code, signal) {
  logger.debug(`Worker ${worker.process.pid} died.\n\nReason: (${signal || code}).\n\nrestarting...`);
  // Respawn a worker when it dies
  cluster.fork();
}

if (cluster.isMaster) {
  // Fork workers
  for (let workers = 0; workers < config.workers; workers++) { // eslint-disable-line
    cluster.fork();
  }
  // Register exit handler
  cluster.on('exit', exitHandler);
  logger.debug('Master process');
} else {
  const server = require('./server');
  server();
}
<|endoftext|>


<commit_before>require.config({
    paths: {
        underscore: 'lib/underscore/underscore',
        jquery:     'lib/jquery/dist/jquery',
        bootstrap:  'lib/bootstrap/dist/js/bootstrap',
        contextmenu:'lib/bootstrap-contextmenu/bootstrap-contextmenu',
        moment:     'lib/moment/moment',
        angular:    'lib/angular/angular',
        ngSanitize: 'lib/angular-sanitize/angular-sanitize',
        ngAnimate:  'lib/angular-animate/angular-animate',
        'angular-moment': 'lib/angular-moment/angular-moment',
        bionode:    'lib/bionode/amd/bionode'
    },
    shim: {
        underscore: {
            exports: '_'
        },
        jquery:     {
            exports: '$'
        },
        bootstrap:  {
            deps:    ['jquery']
        },
        contextmenu: {
            deps:    ['bootstrap']
        },
        angular:    {
            exports: 'angular',
            deps:    ['jquery']
        },
        ngSanitize: {
            deps:    ['angular']
        },
        ngAnimate:  {
            deps:    ['angular']
        }
    },
    packages:[{
        name:     'dojo',
        location: 'lib/dojo'
    },
    {
        name:     'dijit',
        location: 'lib/dijit'
    },
    {
        name:     'dojox',
        location: 'lib/dojox'
    },
    {
        name:     'jszlib',
        location: 'lib/jszlib'
    },
    {
        name:     'dgrid',
        location: 'lib/dgrid'
    },
    {
        name:     'xstyle',
        location: 'lib/xstyle'
    },
    {
        name:     'put-selector',
        location: 'lib/put-selector'
    },
    {
        name:     'FileSaver',
        location: 'lib/FileSaver'
    },
    {
        name:     'jDataView',
        location: 'lib/jDataView/src',
        main:     'jdataview'
    },
    {
        name:     'jqueryui',
        location: 'lib/jquery.ui/ui'
    }],
    map: {
        '*': {
            'less': 'lib/require-less/less',
            'html': 'lib/requirejs-text/text'
        }
    }
});

require(['bootstrap', 'less!styles']
, function () {

    require(['underscore', 'jquery', 'angular', 'dojo/has', 'dojo/_base/sniff', 'ngSanitize', 'ngAnimate', 'angular-moment']
    , function (_, $, angular, has) {

        'use strict';

        var app = angular.module('app', ['ngSanitize', 'ngAnimate', 'angularMoment']);

        app.config(['$httpProvider'
        , function (http_provider) {

            http_provider.interceptors.push(['$q', '$rootScope'
            , function (q, root_scope) {

                return {
                    responseError: function (response) {
                        var status = response.status;
                        switch (status)
                        {
                            case 401:
                                root_scope.view_login();
                            return q.reject(response);

                            default:
                                return q.reject(response);
                        };
                    }
                };
            }]);
        }]);

        app.config(['$locationProvider'
        , function (location_provider) {

            location_provider.html5Mode(true).hashPrefix('!');
        }])

        app.run(['$http', '$location', '$rootScope', '$injector', '$compile'
        , function (http, location, root_scope, injector, compile) {

            if (!has('chrome')) {
                root_scope.not_chrome = true;
            }

            root_scope.view = function (path) {
                var view  = $('.content');
                var scope = view.scope();
                var files = [
                    'html!templates/' + path + '.html',
                    'controllers/'    + path,
                    'less!styles'     // Yeah, we load one and the same style sheet
                                      // for all the views.  A style sheet per view
                                      // just doesn't work.
                ];

                $('#spinner').modal();
                require(files
                , function (template, controller) {

                    scope.$apply(function () {
                        view.html(template);
                        compile(view.contents())(scope);
                        injector.invoke(controller, scope);
                    });
                    $('#spinner').modal('hide');
                });
            };

            root_scope.view_current = function () {
                var path = location.path().split('/')[1];
                if (!path) {
                    path = 'dashboard';
                }
                root_scope.view(path);
            };

            root_scope.view_login = function () {
                root_scope.view('about');
            };

            root_scope.signin_fb = function () {
                FB.login(function (response) {
                    if (response.status === 'connected') {
                        var uid = response.authResponse.userID;
                        var signedRequest = response.authResponse.signedRequest;

                        // get user details
                        FB.api('/' + uid, function (response) {
                            // post to our server who accessed
                            http.post('/signin', JSON.stringify({
                                name:          response.name,
                                email:         response.email,
                                picture:       "http://graph.facebook.com/" + uid + "/picture?type=large",
                                authorization: signedRequest
                            }))
                            .then(function (response) {
                                root_scope.user = response.data;
                                root_scope.view_current();
                            });
                        });
                    }
                    else {
                        $('.login-error').show();
                    }
                }, { scope: 'email' });
            };

            root_scope.signout = function () {
                http.post('signout').
                    then(function () {
                    root_scope.user = undefined;
                    $('#logout').modal('hide');
                    root_scope.view_login();
                });
            };

            root_scope.$on('$locationChangeSuccess'
            , function (event, next, prev) {

                var n = next.split('/').pop().split(/[#?]/);
                var p = prev.split('/').pop().split(/[#?]/);

                http.get('whoami')
                .then(function (response) {
                    var user = response.data;
                    if (!user) {
                        return;
                    }
                    root_scope.user = user;

                    if (n[0] == p[0] && n[1] == p[1] && n[2] != p[2]) {
                        root_scope.$broadcast('$paramsChange', n[2], p[2]);
                        return;
                    }

                    if (n[0] == p[0] && n[1] != p[1]) {
                        root_scope.$broadcast('$hashChange', n[1], p[1]);
                        return;
                    }

                    root_scope.view_current();
                });
            });
        }]);

        angular.bootstrap(document, [app['name']]);
    });
})
<commit_msg>Insert a temporary dumb hack for people who don't have FB account.<commit_after>require.config({
    paths: {
        underscore: 'lib/underscore/underscore',
        jquery:     'lib/jquery/dist/jquery',
        bootstrap:  'lib/bootstrap/dist/js/bootstrap',
        contextmenu:'lib/bootstrap-contextmenu/bootstrap-contextmenu',
        moment:     'lib/moment/moment',
        angular:    'lib/angular/angular',
        ngSanitize: 'lib/angular-sanitize/angular-sanitize',
        ngAnimate:  'lib/angular-animate/angular-animate',
        'angular-moment': 'lib/angular-moment/angular-moment',
        bionode:    'lib/bionode/amd/bionode'
    },
    shim: {
        underscore: {
            exports: '_'
        },
        jquery:     {
            exports: '$'
        },
        bootstrap:  {
            deps:    ['jquery']
        },
        contextmenu: {
            deps:    ['bootstrap']
        },
        angular:    {
            exports: 'angular',
            deps:    ['jquery']
        },
        ngSanitize: {
            deps:    ['angular']
        },
        ngAnimate:  {
            deps:    ['angular']
        }
    },
    packages:[{
        name:     'dojo',
        location: 'lib/dojo'
    },
    {
        name:     'dijit',
        location: 'lib/dijit'
    },
    {
        name:     'dojox',
        location: 'lib/dojox'
    },
    {
        name:     'jszlib',
        location: 'lib/jszlib'
    },
    {
        name:     'dgrid',
        location: 'lib/dgrid'
    },
    {
        name:     'xstyle',
        location: 'lib/xstyle'
    },
    {
        name:     'put-selector',
        location: 'lib/put-selector'
    },
    {
        name:     'FileSaver',
        location: 'lib/FileSaver'
    },
    {
        name:     'jDataView',
        location: 'lib/jDataView/src',
        main:     'jdataview'
    },
    {
        name:     'jqueryui',
        location: 'lib/jquery.ui/ui'
    }],
    map: {
        '*': {
            'less': 'lib/require-less/less',
            'html': 'lib/requirejs-text/text'
        }
    }
});

require(['bootstrap', 'less!styles']
, function () {

    require(['underscore', 'jquery', 'angular', 'dojo/has', 'dojo/_base/sniff', 'ngSanitize', 'ngAnimate', 'angular-moment']
    , function (_, $, angular, has) {

        'use strict';

        var app = angular.module('app', ['ngSanitize', 'ngAnimate', 'angularMoment']);

        app.config(['$httpProvider'
        , function (http_provider) {

            http_provider.interceptors.push(['$q', '$rootScope'
            , function (q, root_scope) {

                return {
                    responseError: function (response) {
                        var status = response.status;
                        switch (status)
                        {
                            case 401:
                                root_scope.signin_em() || root_scope.view_login();
                            return q.reject(response);

                            default:
                                return q.reject(response);
                        };
                    }
                };
            }]);
        }]);

        app.config(['$locationProvider'
        , function (location_provider) {

            location_provider.html5Mode(true).hashPrefix('!');
        }])

        app.run(['$http', '$location', '$rootScope', '$injector', '$compile'
        , function (http, location, root_scope, injector, compile) {

            if (!has('chrome')) {
                root_scope.not_chrome = true;
            }

            root_scope.view = function (path) {
                var view  = $('.content');
                var scope = view.scope();
                var files = [
                    'html!templates/' + path + '.html',
                    'controllers/'    + path,
                    'less!styles'     // Yeah, we load one and the same style sheet
                                      // for all the views.  A style sheet per view
                                      // just doesn't work.
                ];

                $('#spinner').modal();
                require(files
                , function (template, controller) {

                    scope.$apply(function () {
                        view.html(template);
                        compile(view.contents())(scope);
                        injector.invoke(controller, scope);
                    });
                    $('#spinner').modal('hide');
                });
            };

            root_scope.view_current = function () {
                var path = location.path().split('/')[1];
                if (!path) {
                    path = 'dashboard';
                }
                root_scope.view(path);
            };

            root_scope.view_login = function () {
                root_scope.view('about');
            };

            root_scope.signin_em = function () {
                var email = location.search()['login'];
                if (email && email !== true && email !== '') {
                    http.post('/signin', JSON.stringify({
                        email:         email,
                        authorization: ''
                    }))
                    .then(function (response) {
                        root_scope.user = response.data;
                        location.search('login', null);
                        root_scope.view_current();
                    });
                    return true;
                }
                return false;
            };

            root_scope.signin_fb = function () {
                FB.login(function (response) {
                    if (response.status === 'connected') {
                        var uid = response.authResponse.userID;
                        var signedRequest = response.authResponse.signedRequest;

                        // get user details
                        FB.api('/' + uid, function (response) {
                            // post to our server who accessed
                            http.post('/signin', JSON.stringify({
                                name:          response.name,
                                email:         response.email,
                                picture:       "http://graph.facebook.com/" + uid + "/picture?type=large",
                                authorization: signedRequest
                            }))
                            .then(function (response) {
                                root_scope.user = response.data;
                                root_scope.view_current();
                            });
                        });
                    }
                    else {
                        $('.login-error').show();
                    }
                }, { scope: 'email' });
            };

            root_scope.signout = function () {
                http.post('signout').
                    then(function () {
                    root_scope.user = undefined;
                    $('#logout').modal('hide');
                    root_scope.view_login();
                });
            };

            root_scope.$on('$locationChangeSuccess'
            , function (event, next, prev) {

                var n = next.split('/').pop().split(/[#?]/);
                var p = prev.split('/').pop().split(/[#?]/);

                http.get('whoami')
                .then(function (response) {
                    var user = response.data;
                    if (!user) {
                        return;
                    }
                    root_scope.user = user;

                    if (n[0] == p[0] && n[1] == p[1] && n[2] != p[2]) {
                        root_scope.$broadcast('$paramsChange', n[2], p[2]);
                        return;
                    }

                    if (n[0] == p[0] && n[1] != p[1]) {
                        root_scope.$broadcast('$hashChange', n[1], p[1]);
                        return;
                    }

                    root_scope.view_current();
                });
            });
        }]);

        angular.bootstrap(document, [app['name']]);
    });
})
<|endoftext|>


<commit_before>
var argscheck = require('cordova/argscheck'),
    utils = require('cordova/utils'),
    exec = require('cordova/exec');

var gps = function() {

};

gps.prototype.getStatus = function (successCallback, errorCallback) {
    var onsuccess = function(gpsStatus){
      successCallback(JSON.parse(gpsStatus));
    }
    exec(onsuccess, errorCallback, "GPS", "getStatus");
}

gps.prototype.getCurrentLocation = function (successCallback, errorCallback) {
    var onsuccess = function(location){
      successCallback(JSON.parse(location));
    }
    exec(onsuccess, errorCallback, "GPS", "getCurrentLocation");
}

module.exports = gps;
<commit_msg>updated<commit_after>
var argscheck = require('cordova/argscheck'),
    utils = require('cordova/utils'),
    exec = require('cordova/exec');

var gps = function() {

};

gps.prototype.getStatus = gps.getStatus = function (successCallback, errorCallback) {
    var onsuccess = function(gpsStatus){
      successCallback(JSON.parse(gpsStatus));
    }
    exec(onsuccess, errorCallback, "GPS", "getStatus");
}

gps.prototype.getCurrentLocation = function (successCallback, errorCallback) {
    var onsuccess = function(location){
      successCallback(JSON.parse(location));
    }
    exec(onsuccess, errorCallback, "GPS", "getCurrentLocation");
}

module.exports = gps;
<|endoftext|>