<commit_before>// skin.js 0.1.1
// © 2013 Soheil Jadidian
// skin.js may be freely distributed under the MIT license
// http://skinjs.org

define('hub', ['skin', 'adapter'], function(skin, adapter) {
  "use strict";

  // key strings, used in data objects
  var UID       = 'uid'
    , ALIAS     = 'alias'
    , BASE      = 'base'
    , PARENT    = 'parent'
    , NODES     = 'nodes'
    , CALLBACKS = 'callbacks';

  var Hub = skin.Hub = (function() {
    // singleton instance
    var _instance
    function _initialize() {

      // Private Methods and Properties
      // ==============================
        // data objects
      var _cache, _nodes, _templates, _actions, _recipes
        // to split and sanitize index paths
        , _splitter = /[\s.-]/
        // counter for generating unique ids
        , _token = 0;

      // initiate data
      _cache = {};
      _nodes = _set(_cache, [NODES], {});

      // sanitize index, convert multi arguments or chunked string to array
      function _sanitize() {
        var args = arguments
        return (adapter.isArray(args[0]))? args[0]
             : (args.length > 1)? adapter.arraySlice.call(args, 0)
             : (adapter.isString(args[0]))? args[0].split(_splitter)
             : []
      }

      // create unique id for everything
      // zero is reserved for null or undefined
      function _uid(symbol) {
        if (!symbol) return '0';
        if (adapter.isElement(symbol)) {
        }
        if (adapter.isObject(symbol)) return symbol[UID] || (symbol[UID] = ((symbol[ALIAS])? symbol[ALIAS] : '') + ++_token);
        return ((adapter.isString(symbol))? symbol : '') + ++_token;
      }

      function _get(pointer, index) {
        var key, flag;
        if (!index.length) return pointer;
        while (index.length) {
          key = index.shift();
          flag = false;
          if (adapter.objectHas.call(pointer, key)) {
            pointer = pointer[key];
            flag = true;
          } else {
            // check for value in base
            // TODO: implement MAP
            while (adapter.isObject(pointer[BASE])) {
              pointer = pointer[BASE];
              if (adapter.objectHas.call(pointer, key)) {
                pointer = pointer[key];
                flag = true;
                break;
              }
            }
            break;
          }
        }
        return (flag)? pointer : null;
      }

      function _set(pointer, index, value) {
        var key;
        if (!index.length) {
          // if value is a single string and there's no pointer or index
          // we parse the string for special cases
          if (adapter.isString(value)) {
            if (_splitter.test(value)) {
              // TODO: parse strings for some magic!
            } else {
              // only a single key has been passed in, no other arguments
              // create an empty object for that key if it doesn't exist
              pointer[value] || (pointer[value] = {});
              pointer[value][PARENT] = pointer;
              pointer = pointer[value];
            }
          // handle an empty index with an object as value
          // the value can't be assigned to the pointer directly
          // hence, if the value isn't an object we just ignore it
          // if the value is an object, we call set for each of its keys
          } else if (adapter.isObject(value)) for (key in value) _set(pointer, [key], value[key]);
        } else while (index.length) {
          key = index.shift();
          if (index.length) {
            // there are stil deeper levels
            if (!adapter.objectHas.call(pointer, key) || !adapter.isObject(pointer[key])) {
              // isObject() returns false for arrays
              // otherwise the next level object could be pushed in the existing array
              pointer[key] = {};
            }
            pointer[key][PARENT] = pointer;
            pointer = pointer[key];
          } else {
            // no more levels, last key
            // if value is undefined, we remove the key by convension
            // no one wants a key pointing to undefined!
            if (value == undefined) delete pointer[key];
            else if (key != BASE && adapter.isObject(value) && adapter.isObject(pointer[key])) {
              // merge two objects, keep existing reference
              pointer[key][PARENT] = pointer;
              pointer = pointer[key];
              for (key in value) _set(pointer, [key], value[key]);
            } else {
              // replace or create the value
              pointer[key] = value;
              if (adapter.isObject(value)) value[PARENT] = pointer;
              pointer = pointer[key];
            }
          }
        }
        return pointer;
      }

      function _match(target, condition, method) {
        // primary checks
        if (adapter.isUndefined(target)) return false;
        if (adapter.isUndefined(condition) || condition === target) return true;
        // wild card, any value except undefined
        if (condition === '*' && !adapter.isUndefined(target)) return true;
        // match function
        if (adapter.isFunction(condition) && !adapter.isFunction(target)) return condition(target);
        // recursive match for objects
        if (adapter.isObject(condition) && adapter.isObject(target)) {
          var key;
          // simple check if all keys exist for exact or all matching methods
          // before going into costly recursive matching
          if (adapter.isBoolean(method)) for (key in condition) if (adapter.isUndefined(target[key])) return false;
          // now go through every key
          for (key in condition) {
            if (_match(target[key], condition[key], method)) {
              // if method is any and we already have a match
              if (adapter.isUndefined(method)) return true;
            } else {
              // if method is all or exact and we don't have a match
              if (adapter.isBoolean(method)) return false;
            }
          }
          // inverse check, in exact match all target keys should have been covered
          if (method == true) for (key in target) if (adapter.isUndefined(condition[key])) return false;
          return (adapter.isUndefined(method))? false : true;
        }
        // recursive match for arrays
        if (adapter.isArray(condition) && adapter.isArray(target)) {
          var c, t, matched = [];
          // simple check if lengths are equal for exact match
          if (method == true && condition.length != target.length) return false;
          // go through all items for both
          for (c = 0; c < condition.length; c++) {
            for (t = 0; t < target.length; t++) {
              if (matched.indexOf(target[t]) == -1 && _match(target[t], condition[c], method)) {
                if (adapter.isUndefined(method)) return true;
                else matched.push(target[t]);
                break;
              }
            }
            if (matched.length < (c + 1) && adapter.isBoolean(method)) return false;
          }
          return (adapter.isUndefined(method))? false : true;
        }
        // none of above matched
        return false;
      }

      function _search(collection, pointer, condition, recursive) {
        var key;
        if (_match(pointer, condition)) collection.push(pointer);
        // avoid infinite loop, if the child has a reference to parent
        if (recursive && adapter.isObject(pointer)) for (key in pointer) if (key != PARENT) _search(collection, pointer[key], condition, recursive);
      }

      function _find(pointer, condition, recursive) {
        var key, result;
        if (_match(pointer, condition)) return pointer;
        // avoid infinite loop, if the child has a reference to parent
        if (recursive && adapter.isObject(pointer)) for (key in pointer) if (key != PARENT) {
          result = _find(pointer[key], condition, recursive);
          if (result) return result;
        }
        return null;
      }

      function _filter(collection, condition, method) {
        for (var count = collection.length - 1; count >= 0; count--) {
          if (!_match(collection[count], condition, method)) collection.splice(count, 1);
        }
        return collection;
      }

      function _reject(collection, condition, method) {
        for (var count = collection.length - 1; count >= 0; count--) {
          if (_match(collection[count], condition, method)) collection.splice(count, 1);
        }
        return collection;
      }

      function _subscribe(publisher, message, callback) {
        var node, callbacks;
        message.unshift(_uid(publisher));
        node = _get(_nodes, message.slice(0)) || _set(_nodes, message.slice(0), {});
        callbacks = node[CALLBACKS] || (node[CALLBACKS] = []);
        // TODO: check indexOf() is supported
        if (callbacks.indexOf(callback) == -1) callbacks.push(callback);
      }

      function _unsubscribe(publisher, message, callback) {
        var nodes = [], node, callbacks, callback, condition = {};
        message || (message = []);
        message.unshift(_uid(publisher));
        node = _get(_nodes, message.slice(0));
        if (!node) return;
        // set condition and find callbacks in sub branches
        condition[CALLBACKS] = (callback)? [callback] : '*';
        _search(nodes, node, condition, true);
        // removing callbacks
        for (node in nodes) {
          callbacks = nodes[node][CALLBACKS];
          callbacks.splice(callbacks.indexOf(callback), 1)
        }
        // TODO: clean empty branch
      }

      function _publish(publisher, message, parameters) {
        var nodes = [], node, callbacks, callback, condition = {};
        message.unshift(_uid(publisher));
        node = _get(_nodes, message.slice(0));
        // set condition and find callbacks in sub branches
        condition[CALLBACKS] = '*';
        _search(nodes, node, condition, true);
        // calling callbacks
        for (node in nodes) {
          callbacks = nodes[node][CALLBACKS];
          for (callback in callbacks) {
            try {
              // TODO: if a callback returns false break the chain
              callbacks[callback](parameters);
            } catch(exception) {
              throw exception;
            }
          }
        }
      }

      return {

        // Public Methods
        // ==============
        // get the pointer to an index path, related to the starting pointer
        // index path can be an array, or string chunks sliced by . or -
        // returns null if the path doesn't exist
        // example: get(pointer, index)
        get: function() {
          var args = adapter.arraySlice.call(arguments, 0), pointer = _cache, index;
          if (adapter.isObject(args[0])) { pointer = args[0]; args = args.slice(1) }
          index = _sanitize.apply(this, args);
          return _get(pointer, index);
        }

        // set value for an index path, related to the starting pointer
        // index path can be an array, or string chunks sliced by . or -
        // returns pointer to the last modified object or array
        // example: set(pointer, index, value)
      , set: function() {
          var args = adapter.arraySlice.call(arguments, 0), pointer = _cache, index, value;
          // last argument is always the value
          value = args.slice(-1)[0];
          args = args.slice(0, -1);
          // if first argument is object, it is the pointer
          if (adapter.isObject(args[0])) { pointer = args[0]; args = args.slice(1) }
          index = _sanitize.apply(this, args);
          return _set(pointer, index, value);
        }

        // check if target object meets a condition, methods can be any, all and exact
        // method undefined (default) means if any of the conditions matched return true
        // method false, means all of the conditions should match
        // method true, means exact match, hence two given objects should contain exactly the same content tree
        // example: Data.match(target, { foo: 'bar', boo: someObject }, false);
        // TODO: implement BASE and MAP
      , match: function(target, condition, method) {
          return _match(target, condition, method);
        }

        // filter an array, keep items which match a condition
      , filter: function(collection, condition, method) {
          return _filter(collection, condition, method);
        }

        // reject an array, remove items which match a condition
      , reject: function(collection, condition, method) {
          return _reject(collection, condition, method);
        }

        // search and find all children meeting a condition in given pointer, index
        // and return array of pointers to children
        // first arguments are pointer and index, will be passed to get() method
        // followed by the condition object { key: value }
        // last boolean argument indicates if we should search children recursively, default is false
        // example: search(pointer, index, condition, recursive);
      , search: function() {
          var args = adapter.arraySlice.call(arguments, 0), collection = [], pointer, condition, recursive;
          // recursive should be the last argument, if its boolean
          recursive = args.slice(-1)[0];
          if (adapter.isBoolean(recursive)) args = args.slice(0, -1);
          else recursive = false;
          // then the condition
          condition = args.slice(-1)[0];
          args = args.slice(0, -1);
          // remainings can be passed to get()
          pointer = this.get.apply(this, args);
          _search(collection, pointer, condition, recursive);
          return collection;
        }

        // search and find the first child meeting a condition in given pointer, index
        // and return the pointer to the child
        // first arguments are pointer and index, will be passed to get() method
        // followed by the condition object { key: value }
        // last boolean argument indicates if we should search children recursively, default is false
        // example: search(pointer, index, condition, recursive);
      , find: function() {
          var args = adapter.arraySlice.call(arguments, 0), pointer, condition, recursive;
          // recursive should be the last argument, if its boolean
          recursive = args.slice(-1)[0];
          if (adapter.isBoolean(recursive)) args = args.slice(0, -1);
          else recursive = false;
          // then the condition
          condition = args.slice(-1)[0];
          args = args.slice(0, -1);
          // remainings can be passed to get()
          pointer = this.get.apply(this, args);
          return _find(pointer, condition, recursive);
        }

        // example: on(object, event, callback)
      , on: function() {
          var args = adapter.arraySlice.call(arguments, 0), publisher, message, callback;
          if (!adapter.isString(args[0])) { publisher = args[0]; args = args.slice(1); }
          message = _sanitize(args[0]);
          callback = args[1];
          _subscribe(publisher, message, callback);
        }

        // example: off(object, event, callback)
      , off: function() {
          var args = adapter.arraySlice.call(arguments, 0), publisher, message, callback;
          if (!adapter.isString(args[0])) { publisher = args[0]; args = args.slice(1); }
          if (adapter.isString(args[0])) { message = _sanitize(args[0]); args = args.slice(1); }
          if (adapter.isFunction(args[0])) { callback = args[0]; }
          _unsubscribe(publisher, message, callback);
        }

        // example: trigger(object, event, parameters)
      , trigger: function() {
          var args = adapter.arraySlice.call(arguments, 0), publisher, message, parameters;
          if (!adapter.isString(args[0])) { publisher = args[0]; args = args.slice(1); }
          message = _sanitize(args[0]);
          parameters = args[1];
          _publish(publisher, message, parameters);
        }
      }
    }

    // get or create singleton instance
    return {
      getInstance: function() {
        if (!_instance) _instance = _initialize();
        return _instance;
      }
    }
  })()

  return Hub;
});<commit_msg>Hub uid() doesn't store uid on element objects anymore<commit_after>// skin.js 0.1.1
// © 2013 Soheil Jadidian
// skin.js may be freely distributed under the MIT license
// http://skinjs.org

define('hub', ['skin', 'adapter'], function(skin, adapter) {
  "use strict";

  // key strings, used in data objects
  var UID       = 'uid'
    , ALIAS     = 'alias'
    , BASE      = 'base'
    , PARENT    = 'parent'
    , NODES     = 'nodes'
    , CALLBACKS = 'callbacks'
    , ELEMENT   = 'element';

  var Hub = skin.Hub = (function() {
    // singleton instance
    var _instance
    function _initialize() {

      // Private Methods and Properties
      // ==============================
        // data objects
      var _cache, _data, _nodes, _templates, _actions, _recipes
        // to split and sanitize index paths
        , _splitter = /[\s.-]/
        // counter for generating unique ids
        , _token = 0;

      // initiate data
      _cache = {};
      _data  = {};
      _nodes = _set(_data, [NODES], {});

      // sanitize index, convert multi arguments or chunked string to array
      function _sanitize() {
        var args = arguments
        return (adapter.isArray(args[0]))? args[0]
             : (args.length > 1)? adapter.arraySlice.call(args, 0)
             : (adapter.isString(args[0]))? args[0].split(_splitter)
             : []
      }

      // create and retrieve unique id for null, undefined, objects and elements
      // zero is reserved for null or undefined
      function _uid(symbol) {
        if (!symbol) return '0';
        if (adapter.isElement(symbol)) {
          if (symbol === _cache[ELEMENT]) return _cache[UID];
          var node, condition = {};
          _cache[ELEMENT] = condition[ELEMENT] = symbol;
          node = _find(_nodes, condition, true);
          if (!node) {
            node = {};
            node[ELEMENT] = symbol;
            node[UID] = '' + ++_token;
            _set(_nodes, [node[UID]], node);
          }
          return _cache[UID] = node[UID];
        }
        if (adapter.isObject(symbol)) return symbol[UID] || (symbol[UID] = ((symbol[ALIAS])? symbol[ALIAS] : '') + ++_token);
        return ((adapter.isString(symbol))? symbol : '') + ++_token;
      }

      function _get(pointer, index) {
        var key, flag;
        if (!index.length) return pointer;
        while (index.length) {
          key = index.shift();
          flag = false;
          if (adapter.objectHas.call(pointer, key)) {
            pointer = pointer[key];
            flag = true;
          } else {
            // check for value in base
            // TODO: implement MAP
            while (adapter.isObject(pointer[BASE])) {
              pointer = pointer[BASE];
              if (adapter.objectHas.call(pointer, key)) {
                pointer = pointer[key];
                flag = true;
                break;
              }
            }
            break;
          }
        }
        return (flag)? pointer : null;
      }

      function _set(pointer, index, value) {
        var key;
        if (!index.length) {
          // if value is a single string and there's no pointer or index
          // we parse the string for special cases
          if (adapter.isString(value)) {
            if (_splitter.test(value)) {
              // TODO: parse strings for some magic!
            } else {
              // only a single key has been passed in, no other arguments
              // create an empty object for that key if it doesn't exist
              pointer[value] || (pointer[value] = {});
              pointer[value][PARENT] = pointer;
              pointer = pointer[value];
            }
          // handle an empty index with an object as value
          // the value can't be assigned to the pointer directly
          // hence, if the value isn't an object we just ignore it
          // if the value is an object, we call set for each of its keys
          } else if (adapter.isObject(value)) for (key in value) _set(pointer, [key], value[key]);
        } else while (index.length) {
          key = index.shift();
          if (index.length) {
            // there are stil deeper levels
            if (!adapter.objectHas.call(pointer, key) || !adapter.isObject(pointer[key])) {
              // isObject() returns false for arrays
              // otherwise the next level object could be pushed in the existing array
              pointer[key] = {};
            }
            pointer[key][PARENT] = pointer;
            pointer = pointer[key];
          } else {
            // no more levels, last key
            // if value is undefined, we remove the key by convension
            // no one wants a key pointing to undefined!
            if (value == undefined) delete pointer[key];
            else if (key != BASE && adapter.isObject(value) && adapter.isObject(pointer[key])) {
              // merge two objects, keep existing reference
              pointer[key][PARENT] = pointer;
              pointer = pointer[key];
              for (key in value) _set(pointer, [key], value[key]);
            } else {
              // replace or create the value
              pointer[key] = value;
              if (adapter.isObject(value)) value[PARENT] = pointer;
              pointer = pointer[key];
            }
          }
        }
        return pointer;
      }

      function _match(target, condition, method) {
        // primary checks
        if (adapter.isUndefined(target)) return false;
        if (adapter.isUndefined(condition) || condition === target) return true;
        // wild card, any value except undefined
        if (condition === '*' && !adapter.isUndefined(target)) return true;
        // match function
        if (adapter.isFunction(condition) && !adapter.isFunction(target)) return condition(target);
        // recursive match for objects
        if (adapter.isObject(condition) && adapter.isObject(target)) {
          var key;
          // simple check if all keys exist for exact or all matching methods
          // before going into costly recursive matching
          if (adapter.isBoolean(method)) for (key in condition) if (adapter.isUndefined(target[key])) return false;
          // now go through every key
          for (key in condition) {
            if (_match(target[key], condition[key], method)) {
              // if method is any and we already have a match
              if (adapter.isUndefined(method)) return true;
            } else {
              // if method is all or exact and we don't have a match
              if (adapter.isBoolean(method)) return false;
            }
          }
          // inverse check, in exact match all target keys should have been covered
          if (method == true) for (key in target) if (adapter.isUndefined(condition[key])) return false;
          return (adapter.isUndefined(method))? false : true;
        }
        // recursive match for arrays
        if (adapter.isArray(condition) && adapter.isArray(target)) {
          var c, t, matched = [];
          // simple check if lengths are equal for exact match
          if (method == true && condition.length != target.length) return false;
          // go through all items for both
          for (c = 0; c < condition.length; c++) {
            for (t = 0; t < target.length; t++) {
              if (matched.indexOf(target[t]) == -1 && _match(target[t], condition[c], method)) {
                if (adapter.isUndefined(method)) return true;
                else matched.push(target[t]);
                break;
              }
            }
            if (matched.length < (c + 1) && adapter.isBoolean(method)) return false;
          }
          return (adapter.isUndefined(method))? false : true;
        }
        // none of above matched
        return false;
      }

      function _search(collection, pointer, condition, recursive) {
        var key;
        if (_match(pointer, condition)) collection.push(pointer);
        // avoid infinite loop, if the child has a reference to parent
        if (recursive && adapter.isObject(pointer)) for (key in pointer) if (key != PARENT) _search(collection, pointer[key], condition, recursive);
      }

      function _find(pointer, condition, recursive) {
        var key, result;
        if (_match(pointer, condition)) return pointer;
        // avoid infinite loop, if the child has a reference to parent
        if (recursive && adapter.isObject(pointer)) for (key in pointer) if (key != PARENT) {
          result = _find(pointer[key], condition, recursive);
          if (result) return result;
        }
        return null;
      }

      function _filter(collection, condition, method) {
        for (var count = collection.length - 1; count >= 0; count--) {
          if (!_match(collection[count], condition, method)) collection.splice(count, 1);
        }
        return collection;
      }

      function _reject(collection, condition, method) {
        for (var count = collection.length - 1; count >= 0; count--) {
          if (_match(collection[count], condition, method)) collection.splice(count, 1);
        }
        return collection;
      }

      function _subscribe(publisher, message, callback) {
        var node, callbacks;
        message.unshift(_uid(publisher));
        node = _get(_nodes, message.slice(0)) || _set(_nodes, message.slice(0), {});
        callbacks = node[CALLBACKS] || (node[CALLBACKS] = []);
        // TODO: check indexOf() is supported
        if (callbacks.indexOf(callback) == -1) callbacks.push(callback);
      }

      function _unsubscribe(publisher, message, callback) {
        var nodes = [], node, callbacks, callback, condition = {};
        message || (message = []);
        message.unshift(_uid(publisher));
        node = _get(_nodes, message.slice(0));
        if (!node) return;
        // set condition and find callbacks in sub branches
        condition[CALLBACKS] = (callback)? [callback] : '*';
        _search(nodes, node, condition, true);
        // removing callbacks
        for (node in nodes) {
          callbacks = nodes[node][CALLBACKS];
          callbacks.splice(callbacks.indexOf(callback), 1)
        }
        // TODO: clean empty branch
      }

      function _publish(publisher, message, parameters) {
        var nodes = [], node, callbacks, callback, condition = {};
        message.unshift(_uid(publisher));
        node = _get(_nodes, message.slice(0));
        // set condition and find callbacks in sub branches
        condition[CALLBACKS] = '*';
        _search(nodes, node, condition, true);
        // calling callbacks
        for (node in nodes) {
          callbacks = nodes[node][CALLBACKS];
          for (callback in callbacks) {
            try {
              // TODO: if a callback returns false break the chain
              callbacks[callback](parameters);
            } catch(exception) {
              throw exception;
            }
          }
        }
      }

      return {

        // Public Methods
        // ==============
        // get the pointer to an index path, related to the starting pointer
        // index path can be an array, or string chunks sliced by . or -
        // returns null if the path doesn't exist
        // example: get(pointer, index)
        get: function() {
          var args = adapter.arraySlice.call(arguments, 0), pointer = _data, index;
          if (adapter.isObject(args[0])) { pointer = args[0]; args = args.slice(1) }
          index = _sanitize.apply(this, args);
          return _get(pointer, index);
        }

        // set value for an index path, related to the starting pointer
        // index path can be an array, or string chunks sliced by . or -
        // returns pointer to the last modified object or array
        // example: set(pointer, index, value)
      , set: function() {
          var args = adapter.arraySlice.call(arguments, 0), pointer = _data, index, value;
          // last argument is always the value
          value = args.slice(-1)[0];
          args = args.slice(0, -1);
          // if first argument is object, it is the pointer
          if (adapter.isObject(args[0])) { pointer = args[0]; args = args.slice(1) }
          index = _sanitize.apply(this, args);
          return _set(pointer, index, value);
        }

        // check if target object meets a condition, methods can be any, all and exact
        // method undefined (default) means if any of the conditions matched return true
        // method false, means all of the conditions should match
        // method true, means exact match, hence two given objects should contain exactly the same content tree
        // example: Data.match(target, { foo: 'bar', boo: someObject }, false);
        // TODO: implement BASE and MAP
      , match: function(target, condition, method) {
          return _match(target, condition, method);
        }

        // filter an array, keep items which match a condition
      , filter: function(collection, condition, method) {
          return _filter(collection, condition, method);
        }

        // reject an array, remove items which match a condition
      , reject: function(collection, condition, method) {
          return _reject(collection, condition, method);
        }

        // search and find all children meeting a condition in given pointer, index
        // and return array of pointers to children
        // first arguments are pointer and index, will be passed to get() method
        // followed by the condition object { key: value }
        // last boolean argument indicates if we should search children recursively, default is false
        // example: search(pointer, index, condition, recursive);
      , search: function() {
          var args = adapter.arraySlice.call(arguments, 0), collection = [], pointer, condition, recursive;
          // recursive should be the last argument, if its boolean
          recursive = args.slice(-1)[0];
          if (adapter.isBoolean(recursive)) args = args.slice(0, -1);
          else recursive = false;
          // then the condition
          condition = args.slice(-1)[0];
          args = args.slice(0, -1);
          // remainings can be passed to get()
          pointer = this.get.apply(this, args);
          _search(collection, pointer, condition, recursive);
          return collection;
        }

        // search and find the first child meeting a condition in given pointer, index
        // and return the pointer to the child
        // first arguments are pointer and index, will be passed to get() method
        // followed by the condition object { key: value }
        // last boolean argument indicates if we should search children recursively, default is false
        // example: search(pointer, index, condition, recursive);
      , find: function() {
          var args = adapter.arraySlice.call(arguments, 0), pointer, condition, recursive;
          // recursive should be the last argument, if its boolean
          recursive = args.slice(-1)[0];
          if (adapter.isBoolean(recursive)) args = args.slice(0, -1);
          else recursive = false;
          // then the condition
          condition = args.slice(-1)[0];
          args = args.slice(0, -1);
          // remainings can be passed to get()
          pointer = this.get.apply(this, args);
          return _find(pointer, condition, recursive);
        }

        // example: on(object, event, callback)
      , on: function() {
          var args = adapter.arraySlice.call(arguments, 0), publisher, message, callback;
          if (!adapter.isString(args[0])) { publisher = args[0]; args = args.slice(1); }
          message = _sanitize(args[0]);
          callback = args[1];
          _subscribe(publisher, message, callback);
        }

        // example: off(object, event, callback)
      , off: function() {
          var args = adapter.arraySlice.call(arguments, 0), publisher, message, callback;
          if (!adapter.isString(args[0])) { publisher = args[0]; args = args.slice(1); }
          if (adapter.isString(args[0])) { message = _sanitize(args[0]); args = args.slice(1); }
          if (adapter.isFunction(args[0])) { callback = args[0]; }
          _unsubscribe(publisher, message, callback);
        }

        // example: trigger(object, event, parameters)
      , trigger: function() {
          var args = adapter.arraySlice.call(arguments, 0), publisher, message, parameters;
          if (!adapter.isString(args[0])) { publisher = args[0]; args = args.slice(1); }
          message = _sanitize(args[0]);
          parameters = args[1];
          _publish(publisher, message, parameters);
        }
      }
    }

    // get or create singleton instance
    return {
      getInstance: function() {
        if (!_instance) _instance = _initialize();
        return _instance;
      }
    }
  })()

  return Hub;
});<|endoftext|>


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

  angular
    .module('Portfolio')
    .factory('projectData', projectData);

  projectData.$inject = ['$http'];

  /* @ngInject */
  function projectData($http) {
    var service = {
      all: getAll,
      getFirst: getFirst
    };

    return service;

    function getAll() {
      return $http({
        method: 'GET',
        url: 'data/projects.json'
      });
    }

    function getFirst() {
        return $http({
            method: 'GET',
            url: 'http://localhost:3000/contacts/1'
        });
    }
  }
})();
<commit_msg>commented experimental rails api out.<commit_after>(function() {
  'use strict';

  angular
    .module('Portfolio')
    .factory('projectData', projectData);

  projectData.$inject = ['$http'];

  /* @ngInject */
  function projectData($http) {
    var service = {
      all: getAll,
      getFirst: getFirst
    };

    return service;

    function getAll() {
      return $http({
        method: 'GET',
        url: 'data/projects.json'
      });
    }

    // ----- EXPERIMENTAL RAILS API -----
    // function getFirst() {
    //     return $http({
    //         method: 'GET',
    //         url: 'http://localhost:3000/contacts/1'
    //     });
    // }
  }
})();
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as CmsEditorActionCreators from './actions';
import cssModules from 'react-css-modules';
import styles from './index.module.scss';
import { CmsEditor, ToastMessage } from 'components';

class CmsEditorContainer extends Component {
  constructor() {
    super();
    this.handleSubmit = this.handleSubmit.bind(this);
    this.handleCloseToast = this.handleCloseToast.bind(this);
  }
  handleSubmit(data) {
    const {
      submitArticleRequest,
    } = this.props.actions;
    submitArticleRequest(data);
  }
  handleCloseToast({ type }) {
    const {
      handleClearingToast,
    } = this.props.actions;
    handleClearingToast(type);
  }
  render() {
    const {
      error,
      message,
    } = this.props;
    return (

        {error &&
          <ToastMessage
            message={error.message ? error.message : error}
            onClose={() => this.handleCloseToast({ type: 'error' })}
            status="critical"
          />
        }
        {message &&
          <ToastMessage
            message={message}
            onClose={() => this.handleCloseToast({ type: 'message' })}
          />
        }
        <CmsEditor
          onSubmit={this.handleSubmit}
        />

    );
  }
}

CmsEditorContainer.propTypes = {
  actions: PropTypes.object.isRequired,
  error: PropTypes.object,
  message: PropTypes.string,
};

// mapStateToProps :: {State} -> {Props}
const mapStateToProps = (state) => ({
  error: state.cmsEditorContainer.error,
  message: state.cmsEditorContainer.message,
  isSubmitting: state.cmsEditorContainer.isSubmitting,
});

// mapDispatchToProps :: Dispatch -> {Action}
const mapDispatchToProps = (dispatch) => ({
  actions: bindActionCreators(
    CmsEditorActionCreators,
    dispatch
  ),
});

const Container = cssModules(CmsEditorContainer, styles);

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Container);
<commit_msg>Feat: implementing redirecting to ContentDashboard after article submission; redirect to admin's for now; need to go to individual user when user route available<commit_after>import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as CmsEditorActionCreators from './actions';
import cssModules from 'react-css-modules';
import styles from './index.module.scss';
import { CmsEditor, ToastMessage } from 'components';

class CmsEditorContainer extends Component {
  constructor() {
    super();
    this.handleSubmit = this.handleSubmit.bind(this);
    this.handleCloseToast = this.handleCloseToast.bind(this);
  }

  handleSubmit(data) {
    const {
      submitArticleRequest,
    } = this.props.actions;
    submitArticleRequest(data);
    const path = `/admin/content-dashboard`;
    // this.context.router.push(path);
    const {
      router,
    } = this.context;
    router.push(path);
  }
  handleCloseToast({ type }) {
    const {
      handleClearingToast,
    } = this.props.actions;
    handleClearingToast(type);
  }
  render() {
    const {
      error,
      message,
    } = this.props;
    return (

        {error &&
          <ToastMessage
            message={error.message ? error.message : error}
            onClose={() => this.handleCloseToast({ type: 'error' })}
            status="critical"
          />
        }
        {message &&
          <ToastMessage
            message={message}
            onClose={() => this.handleCloseToast({ type: 'message' })}
          />
        }
        <CmsEditor
          onSubmit={this.handleSubmit}
        />

    );
  }
}

CmsEditorContainer.propTypes = {
  actions: PropTypes.object.isRequired,
  error: PropTypes.object,
  message: PropTypes.string,
};

CmsEditorContainer.contextTypes = {
  router: PropTypes.object.isRequired,
};

// mapStateToProps :: {State} -> {Props}
const mapStateToProps = (state) => ({
  error: state.cmsEditorContainer.error,
  message: state.cmsEditorContainer.message,
  isSubmitting: state.cmsEditorContainer.isSubmitting,
});

// mapDispatchToProps :: Dispatch -> {Action}
const mapDispatchToProps = (dispatch) => ({
  actions: bindActionCreators(
    CmsEditorActionCreators,
    dispatch
  ),
});

const Container = cssModules(CmsEditorContainer, styles);

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Container);
<|endoftext|>


<commit_before>
const assert = require('assert')
const http = require('http')
const path = require('path')
const ws = require('ws')
const remote = require('electron').remote

const BrowserWindow = remote.require('electron').BrowserWindow
const session = remote.require('electron').session

const isCI = remote.getGlobal('isCi')

describe('chromium feature', function () {
  var fixtures = path.resolve(__dirname, 'fixtures')
  var listener = null

  afterEach(function () {
    if (listener != null) {
      window.removeEventListener('message', listener)
    }
    listener = null
  })

  xdescribe('heap snapshot', function () {
    it('does not crash', function () {
      process.atomBinding('v8_util').takeHeapSnapshot()
    })
  })

  describe('sending request of http protocol urls', function () {
    it('does not crash', function (done) {
      this.timeout(5000)

      var server = http.createServer(function (req, res) {
        res.end()
        server.close()
        done()
      })
      server.listen(0, '127.0.0.1', function () {
        var port = server.address().port
        $.get('http://127.0.0.1:' + port)
      })
    })
  })

  describe('document.hidden', function () {
    var url = 'file://' + fixtures + '/pages/document-hidden.html'
    var w = null

    afterEach(function () {
      w != null ? w.destroy() : void 0
    })

    it('is set correctly when window is not shown', function (done) {
      w = new BrowserWindow({
        show: false
      })
      w.webContents.on('ipc-message', function (event, args) {
        assert.deepEqual(args, ['hidden', true])
        done()
      })
      w.loadURL(url)
    })

    it('is set correctly when window is inactive', function (done) {
      w = new BrowserWindow({
        show: false
      })
      w.webContents.on('ipc-message', function (event, args) {
        assert.deepEqual(args, ['hidden', false])
        done()
      })
      w.showInactive()
      w.loadURL(url)
    })
  })

  xdescribe('navigator.webkitGetUserMedia', function () {
    it('calls its callbacks', function (done) {
      this.timeout(5000)

      navigator.webkitGetUserMedia({
        audio: true,
        video: false
      }, function () {
        done()
      }, function () {
        done()
      })
    })
  })

  describe('navigator.mediaDevices', function () {
    if (process.env.TRAVIS === 'true') {
      return
    }
    if (isCI && process.platform === 'linux') {
      return
    }

    it('can return labels of enumerated devices', function (done) {
      navigator.mediaDevices.enumerateDevices().then((devices) => {
        const labels = devices.map((device) => device.label)
        const labelFound = labels.some((label) => !!label)
        if (labelFound) {
          done()
        } else {
          done('No device labels found: ' + JSON.stringify(labels))
        }
      }).catch(done)
    })
  })

  describe('navigator.language', function () {
    it('should not be empty', function () {
      assert.notEqual(navigator.language, '')
    })
  })

  describe('navigator.serviceWorker', function () {
    var url = 'file://' + fixtures + '/pages/service-worker/index.html'
    var w = null

    afterEach(function () {
      w != null ? w.destroy() : void 0
    })

    it('should register for file scheme', function (done) {
      w = new BrowserWindow({
        show: false
      })
      w.webContents.on('ipc-message', function (event, args) {
        if (args[0] === 'reload') {
          w.webContents.reload()
        } else if (args[0] === 'error') {
          done('unexpected error : ' + args[1])
        } else if (args[0] === 'response') {
          assert.equal(args[1], 'Hello from serviceWorker!')
          session.defaultSession.clearStorageData({
            storages: ['serviceworkers']
          }, function () {
            done()
          })
        }
      })
      w.loadURL(url)
    })
  })

  describe('window.open', function () {
    this.timeout(20000)

    it('returns a BrowserWindowProxy object', function () {
      var b = window.open('about:blank', '', 'show=no')
      assert.equal(b.closed, false)
      assert.equal(b.constructor.name, 'BrowserWindowProxy')
      b.close()
    })

    it('accepts "nodeIntegration" as feature', function (done) {
      var b
      listener = function (event) {
        assert.equal(event.data, 'undefined')
        b.close()
        done()
      }
      window.addEventListener('message', listener)
      b = window.open('file://' + fixtures + '/pages/window-opener-node.html', '', 'nodeIntegration=no,show=no')
    })

    it('inherit options of parent window', function (done) {
      var b
      listener = function (event) {
        var ref1 = remote.getCurrentWindow().getSize()
        var width = ref1[0]
        var height = ref1[1]
        assert.equal(event.data, 'size: ' + width + ' ' + height)
        b.close()
        done()
      }
      window.addEventListener('message', listener)
      b = window.open('file://' + fixtures + '/pages/window-open-size.html', '', 'show=no')
    })

    it('disables node integration when it is disabled on the parent window', function (done) {
      var b
      listener = function (event) {
        assert.equal(event.data, 'undefined')
        b.close()
        done()
      }
      window.addEventListener('message', listener)

      var windowUrl = require('url').format({
        pathname: fixtures + "/pages/window-opener-no-node-integration.html",
        protocol: 'file',
        query: {
          p: fixtures + "/pages/window-opener-node.html"
        },
        slashes: true
      })
      console.log(windowUrl)
      b = window.open(windowUrl, 'nodeIntegration=no,show=no')
    })

    it('does not override child options', function (done) {
      var b, size
      size = {
        width: 350,
        height: 450
      }
      listener = function (event) {
        assert.equal(event.data, 'size: ' + size.width + ' ' + size.height)
        b.close()
        done()
      }
      window.addEventListener('message', listener)
      b = window.open('file://' + fixtures + '/pages/window-open-size.html', '', 'show=no,width=' + size.width + ',height=' + size.height)
    })

    it('defines a window.location getter', function (done) {
      var b, targetURL
      if (process.platform == 'win32')
        targetURL = 'file:///' + fixtures.replace(/\\/g, '/') + '/pages/base-page.html'
      else
        targetURL = 'file://' + fixtures + '/pages/base-page.html'
      b = window.open(targetURL)
      BrowserWindow.fromId(b.guestId).webContents.once('did-finish-load', function () {
        assert.equal(b.location, targetURL)
        b.close()
        done()
      })
    })

    it('defines a window.location setter', function (done) {
      // Load a page that definitely won't redirect
      var b
      b = window.open('about:blank')
      BrowserWindow.fromId(b.guestId).webContents.once('did-finish-load', function () {
        // When it loads, redirect
        b.location = 'file://' + fixtures + '/pages/base-page.html'
        BrowserWindow.fromId(b.guestId).webContents.once('did-finish-load', function () {
          // After our second redirect, cleanup and callback
          b.close()
          done()
        })
      })
    })
  })

  describe('window.opener', function () {
    this.timeout(10000)

    var url = 'file://' + fixtures + '/pages/window-opener.html'
    var w = null

    afterEach(function () {
      w != null ? w.destroy() : void 0
    })

    it('is null for main window', function (done) {
      w = new BrowserWindow({
        show: false
      })
      w.webContents.on('ipc-message', function (event, args) {
        assert.deepEqual(args, ['opener', null])
        done()
      })
      w.loadURL(url)
    })

    it('is not null for window opened by window.open', function (done) {
      var b
      listener = function (event) {
        assert.equal(event.data, 'object')
        b.close()
        done()
      }
      window.addEventListener('message', listener)
      b = window.open(url, '', 'show=no')
    })
  })

  describe('window.postMessage', function () {
    it('sets the source and origin correctly', function (done) {
      var b, sourceId
      sourceId = remote.getCurrentWindow().id
      listener = function (event) {
        window.removeEventListener('message', listener)
        b.close()
        var message = JSON.parse(event.data)
        assert.equal(message.data, 'testing')
        assert.equal(message.origin, 'file://')
        assert.equal(message.sourceEqualsOpener, true)
        assert.equal(message.sourceId, sourceId)
        assert.equal(event.origin, 'file://')
        done()
      }
      window.addEventListener('message', listener)
      b = window.open('file://' + fixtures + '/pages/window-open-postMessage.html', '', 'show=no')
      BrowserWindow.fromId(b.guestId).webContents.once('did-finish-load', function () {
        b.postMessage('testing', '*')
      })
    })
  })

  describe('window.opener.postMessage', function () {
    it('sets source and origin correctly', function (done) {
      var b
      listener = function (event) {
        window.removeEventListener('message', listener)
        b.close()
        assert.equal(event.source, b)
        assert.equal(event.origin, 'file://')
        done()
      }
      window.addEventListener('message', listener)
      b = window.open('file://' + fixtures + '/pages/window-opener-postMessage.html', '', 'show=no')
    })
  })

  describe('creating a Uint8Array under browser side', function () {
    it('does not crash', function () {
      var RUint8Array = remote.getGlobal('Uint8Array')
      var arr = new RUint8Array()
      assert(arr)
    })
  })

  describe('webgl', function () {
    it('can be get as context in canvas', function () {
      if (process.platform === 'linux') return

      var webgl = document.createElement('canvas').getContext('webgl')
      assert.notEqual(webgl, null)
    })
  })

  describe('web workers', function () {
    it('Worker can work', function (done) {
      var worker = new Worker('../fixtures/workers/worker.js')
      var message = 'ping'
      worker.onmessage = function (event) {
        assert.equal(event.data, message)
        worker.terminate()
        done()
      }
      worker.postMessage(message)
    })

    it('SharedWorker can work', function (done) {
      var worker = new SharedWorker('../fixtures/workers/shared_worker.js')
      var message = 'ping'
      worker.port.onmessage = function (event) {
        assert.equal(event.data, message)
        done()
      }
      worker.port.postMessage(message)
    })
  })

  describe('iframe', function () {
    var iframe = null

    beforeEach(function () {
      iframe = document.createElement('iframe')
    })

    afterEach(function () {
      document.body.removeChild(iframe)
    })

    it('does not have node integration', function (done) {
      iframe.src = 'file://' + fixtures + '/pages/set-global.html'
      document.body.appendChild(iframe)
      iframe.onload = function () {
        assert.equal(iframe.contentWindow.test, 'undefined undefined undefined')
        done()
      }
    })
  })

  describe('storage', function () {
    it('requesting persitent quota works', function (done) {
      navigator.webkitPersistentStorage.requestQuota(1024 * 1024, function (grantedBytes) {
        assert.equal(grantedBytes, 1048576)
        done()
      })
    })
  })

  describe('websockets', function () {
    var wss = null
    var server = null
    var WebSocketServer = ws.Server

    afterEach(function () {
      wss.close()
      server.close()
    })

    it('has user agent', function (done) {
      server = http.createServer()
      server.listen(0, '127.0.0.1', function () {
        var port = server.address().port
        wss = new WebSocketServer({
          server: server
        })
        wss.on('error', done)
        wss.on('connection', function (ws) {
          if (ws.upgradeReq.headers['user-agent']) {
            done()
          } else {
            done('user agent is empty')
          }
        })
        var socket = new WebSocket(`ws://127.0.0.1:${port}`)
        assert(socket)
      })
    })
  })

  describe('Promise', function () {
    it('resolves correctly in Node.js calls', function (done) {
      document.registerElement('x-element', {
        prototype: Object.create(HTMLElement.prototype, {
          createdCallback: {
            value: function () {}
          }
        })
      })
      setImmediate(function () {
        var called = false
        Promise.resolve().then(function () {
          done(called ? void 0 : new Error('wrong sequence'))
        })
        document.createElement('x-element')
        called = true
      })
    })

    it('resolves correctly in Electron calls', function (done) {
      document.registerElement('y-element', {
        prototype: Object.create(HTMLElement.prototype, {
          createdCallback: {
            value: function () {}
          }
        })
      })
      remote.getGlobal('setImmediate')(function () {
        var called = false
        Promise.resolve().then(function () {
          done(called ? void 0 : new Error('wrong sequence'))
        })
        document.createElement('y-element')
        called = true
      })
    })
  })
})
<commit_msg>Remove stray log<commit_after>
const assert = require('assert')
const http = require('http')
const path = require('path')
const ws = require('ws')
const remote = require('electron').remote

const BrowserWindow = remote.require('electron').BrowserWindow
const session = remote.require('electron').session

const isCI = remote.getGlobal('isCi')

describe('chromium feature', function () {
  var fixtures = path.resolve(__dirname, 'fixtures')
  var listener = null

  afterEach(function () {
    if (listener != null) {
      window.removeEventListener('message', listener)
    }
    listener = null
  })

  xdescribe('heap snapshot', function () {
    it('does not crash', function () {
      process.atomBinding('v8_util').takeHeapSnapshot()
    })
  })

  describe('sending request of http protocol urls', function () {
    it('does not crash', function (done) {
      this.timeout(5000)

      var server = http.createServer(function (req, res) {
        res.end()
        server.close()
        done()
      })
      server.listen(0, '127.0.0.1', function () {
        var port = server.address().port
        $.get('http://127.0.0.1:' + port)
      })
    })
  })

  describe('document.hidden', function () {
    var url = 'file://' + fixtures + '/pages/document-hidden.html'
    var w = null

    afterEach(function () {
      w != null ? w.destroy() : void 0
    })

    it('is set correctly when window is not shown', function (done) {
      w = new BrowserWindow({
        show: false
      })
      w.webContents.on('ipc-message', function (event, args) {
        assert.deepEqual(args, ['hidden', true])
        done()
      })
      w.loadURL(url)
    })

    it('is set correctly when window is inactive', function (done) {
      w = new BrowserWindow({
        show: false
      })
      w.webContents.on('ipc-message', function (event, args) {
        assert.deepEqual(args, ['hidden', false])
        done()
      })
      w.showInactive()
      w.loadURL(url)
    })
  })

  xdescribe('navigator.webkitGetUserMedia', function () {
    it('calls its callbacks', function (done) {
      this.timeout(5000)

      navigator.webkitGetUserMedia({
        audio: true,
        video: false
      }, function () {
        done()
      }, function () {
        done()
      })
    })
  })

  describe('navigator.mediaDevices', function () {
    if (process.env.TRAVIS === 'true') {
      return
    }
    if (isCI && process.platform === 'linux') {
      return
    }

    it('can return labels of enumerated devices', function (done) {
      navigator.mediaDevices.enumerateDevices().then((devices) => {
        const labels = devices.map((device) => device.label)
        const labelFound = labels.some((label) => !!label)
        if (labelFound) {
          done()
        } else {
          done('No device labels found: ' + JSON.stringify(labels))
        }
      }).catch(done)
    })
  })

  describe('navigator.language', function () {
    it('should not be empty', function () {
      assert.notEqual(navigator.language, '')
    })
  })

  describe('navigator.serviceWorker', function () {
    var url = 'file://' + fixtures + '/pages/service-worker/index.html'
    var w = null

    afterEach(function () {
      w != null ? w.destroy() : void 0
    })

    it('should register for file scheme', function (done) {
      w = new BrowserWindow({
        show: false
      })
      w.webContents.on('ipc-message', function (event, args) {
        if (args[0] === 'reload') {
          w.webContents.reload()
        } else if (args[0] === 'error') {
          done('unexpected error : ' + args[1])
        } else if (args[0] === 'response') {
          assert.equal(args[1], 'Hello from serviceWorker!')
          session.defaultSession.clearStorageData({
            storages: ['serviceworkers']
          }, function () {
            done()
          })
        }
      })
      w.loadURL(url)
    })
  })

  describe('window.open', function () {
    this.timeout(20000)

    it('returns a BrowserWindowProxy object', function () {
      var b = window.open('about:blank', '', 'show=no')
      assert.equal(b.closed, false)
      assert.equal(b.constructor.name, 'BrowserWindowProxy')
      b.close()
    })

    it('accepts "nodeIntegration" as feature', function (done) {
      var b
      listener = function (event) {
        assert.equal(event.data, 'undefined')
        b.close()
        done()
      }
      window.addEventListener('message', listener)
      b = window.open('file://' + fixtures + '/pages/window-opener-node.html', '', 'nodeIntegration=no,show=no')
    })

    it('inherit options of parent window', function (done) {
      var b
      listener = function (event) {
        var ref1 = remote.getCurrentWindow().getSize()
        var width = ref1[0]
        var height = ref1[1]
        assert.equal(event.data, 'size: ' + width + ' ' + height)
        b.close()
        done()
      }
      window.addEventListener('message', listener)
      b = window.open('file://' + fixtures + '/pages/window-open-size.html', '', 'show=no')
    })

    it('disables node integration when it is disabled on the parent window', function (done) {
      var b
      listener = function (event) {
        assert.equal(event.data, 'undefined')
        b.close()
        done()
      }
      window.addEventListener('message', listener)

      var windowUrl = require('url').format({
        pathname: fixtures + "/pages/window-opener-no-node-integration.html",
        protocol: 'file',
        query: {
          p: fixtures + "/pages/window-opener-node.html"
        },
        slashes: true
      })
      b = window.open(windowUrl, 'nodeIntegration=no,show=no')
    })

    it('does not override child options', function (done) {
      var b, size
      size = {
        width: 350,
        height: 450
      }
      listener = function (event) {
        assert.equal(event.data, 'size: ' + size.width + ' ' + size.height)
        b.close()
        done()
      }
      window.addEventListener('message', listener)
      b = window.open('file://' + fixtures + '/pages/window-open-size.html', '', 'show=no,width=' + size.width + ',height=' + size.height)
    })

    it('defines a window.location getter', function (done) {
      var b, targetURL
      if (process.platform == 'win32')
        targetURL = 'file:///' + fixtures.replace(/\\/g, '/') + '/pages/base-page.html'
      else
        targetURL = 'file://' + fixtures + '/pages/base-page.html'
      b = window.open(targetURL)
      BrowserWindow.fromId(b.guestId).webContents.once('did-finish-load', function () {
        assert.equal(b.location, targetURL)
        b.close()
        done()
      })
    })

    it('defines a window.location setter', function (done) {
      // Load a page that definitely won't redirect
      var b
      b = window.open('about:blank')
      BrowserWindow.fromId(b.guestId).webContents.once('did-finish-load', function () {
        // When it loads, redirect
        b.location = 'file://' + fixtures + '/pages/base-page.html'
        BrowserWindow.fromId(b.guestId).webContents.once('did-finish-load', function () {
          // After our second redirect, cleanup and callback
          b.close()
          done()
        })
      })
    })
  })

  describe('window.opener', function () {
    this.timeout(10000)

    var url = 'file://' + fixtures + '/pages/window-opener.html'
    var w = null

    afterEach(function () {
      w != null ? w.destroy() : void 0
    })

    it('is null for main window', function (done) {
      w = new BrowserWindow({
        show: false
      })
      w.webContents.on('ipc-message', function (event, args) {
        assert.deepEqual(args, ['opener', null])
        done()
      })
      w.loadURL(url)
    })

    it('is not null for window opened by window.open', function (done) {
      var b
      listener = function (event) {
        assert.equal(event.data, 'object')
        b.close()
        done()
      }
      window.addEventListener('message', listener)
      b = window.open(url, '', 'show=no')
    })
  })

  describe('window.postMessage', function () {
    it('sets the source and origin correctly', function (done) {
      var b, sourceId
      sourceId = remote.getCurrentWindow().id
      listener = function (event) {
        window.removeEventListener('message', listener)
        b.close()
        var message = JSON.parse(event.data)
        assert.equal(message.data, 'testing')
        assert.equal(message.origin, 'file://')
        assert.equal(message.sourceEqualsOpener, true)
        assert.equal(message.sourceId, sourceId)
        assert.equal(event.origin, 'file://')
        done()
      }
      window.addEventListener('message', listener)
      b = window.open('file://' + fixtures + '/pages/window-open-postMessage.html', '', 'show=no')
      BrowserWindow.fromId(b.guestId).webContents.once('did-finish-load', function () {
        b.postMessage('testing', '*')
      })
    })
  })

  describe('window.opener.postMessage', function () {
    it('sets source and origin correctly', function (done) {
      var b
      listener = function (event) {
        window.removeEventListener('message', listener)
        b.close()
        assert.equal(event.source, b)
        assert.equal(event.origin, 'file://')
        done()
      }
      window.addEventListener('message', listener)
      b = window.open('file://' + fixtures + '/pages/window-opener-postMessage.html', '', 'show=no')
    })
  })

  describe('creating a Uint8Array under browser side', function () {
    it('does not crash', function () {
      var RUint8Array = remote.getGlobal('Uint8Array')
      var arr = new RUint8Array()
      assert(arr)
    })
  })

  describe('webgl', function () {
    it('can be get as context in canvas', function () {
      if (process.platform === 'linux') return

      var webgl = document.createElement('canvas').getContext('webgl')
      assert.notEqual(webgl, null)
    })
  })

  describe('web workers', function () {
    it('Worker can work', function (done) {
      var worker = new Worker('../fixtures/workers/worker.js')
      var message = 'ping'
      worker.onmessage = function (event) {
        assert.equal(event.data, message)
        worker.terminate()
        done()
      }
      worker.postMessage(message)
    })

    it('SharedWorker can work', function (done) {
      var worker = new SharedWorker('../fixtures/workers/shared_worker.js')
      var message = 'ping'
      worker.port.onmessage = function (event) {
        assert.equal(event.data, message)
        done()
      }
      worker.port.postMessage(message)
    })
  })

  describe('iframe', function () {
    var iframe = null

    beforeEach(function () {
      iframe = document.createElement('iframe')
    })

    afterEach(function () {
      document.body.removeChild(iframe)
    })

    it('does not have node integration', function (done) {
      iframe.src = 'file://' + fixtures + '/pages/set-global.html'
      document.body.appendChild(iframe)
      iframe.onload = function () {
        assert.equal(iframe.contentWindow.test, 'undefined undefined undefined')
        done()
      }
    })
  })

  describe('storage', function () {
    it('requesting persitent quota works', function (done) {
      navigator.webkitPersistentStorage.requestQuota(1024 * 1024, function (grantedBytes) {
        assert.equal(grantedBytes, 1048576)
        done()
      })
    })
  })

  describe('websockets', function () {
    var wss = null
    var server = null
    var WebSocketServer = ws.Server

    afterEach(function () {
      wss.close()
      server.close()
    })

    it('has user agent', function (done) {
      server = http.createServer()
      server.listen(0, '127.0.0.1', function () {
        var port = server.address().port
        wss = new WebSocketServer({
          server: server
        })
        wss.on('error', done)
        wss.on('connection', function (ws) {
          if (ws.upgradeReq.headers['user-agent']) {
            done()
          } else {
            done('user agent is empty')
          }
        })
        var socket = new WebSocket(`ws://127.0.0.1:${port}`)
        assert(socket)
      })
    })
  })

  describe('Promise', function () {
    it('resolves correctly in Node.js calls', function (done) {
      document.registerElement('x-element', {
        prototype: Object.create(HTMLElement.prototype, {
          createdCallback: {
            value: function () {}
          }
        })
      })
      setImmediate(function () {
        var called = false
        Promise.resolve().then(function () {
          done(called ? void 0 : new Error('wrong sequence'))
        })
        document.createElement('x-element')
        called = true
      })
    })

    it('resolves correctly in Electron calls', function (done) {
      document.registerElement('y-element', {
        prototype: Object.create(HTMLElement.prototype, {
          createdCallback: {
            value: function () {}
          }
        })
      })
      remote.getGlobal('setImmediate')(function () {
        var called = false
        Promise.resolve().then(function () {
          done(called ? void 0 : new Error('wrong sequence'))
        })
        document.createElement('y-element')
        called = true
      })
    })
  })
})
<|endoftext|>


<commit_before>const m = require('mithril');

const Nav = module.exports = {};
<commit_msg>Add hierarchical nav items<commit_after>const m = require('mithril');

const Nav = module.exports = {};

Nav.navItems = [
  {
    'name': 'Metrics', 'href': '/metrics',
    'sub': [
      {'name': 'Overview', 'href': '/metrics/overview'},
      {'name': 'Real-Time', 'href': '/metrics/realtime'},
      {'name': 'Demographics', 'href': '/metrics/demographics'}
    ]
  },
  {
    'name': 'Streams', 'href': '/streams',
    'sub': [
      {'name': 'Live Streams', 'href': '/streams/live'},
      {'name': 'All Streams', 'href': '/streams/all'},
      {'name': 'Search', 'href': '/streams/search'}
    ]
  },
  {
    'name': 'Users', 'href': '/users',
    'sub': [
      {'name': 'All Users', 'href': '/users/all'},
      {'name': 'Search', 'href': '/users/search'}
    ]
  },
  {
    'name': 'Admins', 'href': '/admins',
    'sub': [
      {'name': 'All Admins', 'href': '/admins/all'},
      {'name': 'Create Admin', 'href': '/admins/create'}
    ]
  },
  {
    'name': 'Settings',
    'href': '/settings',
    'sub': []
  }
];
<|endoftext|>


<commit_before>/** @babel */

import helpers from './spec-helpers'
import fs from 'fs-plus'
import path from 'path'
import werkzeug from '../lib/werkzeug'
import Composer from '../lib/composer'

describe('Composer', () => {
  beforeEach(() => {
    waitsForPromise(() => helpers.activatePackages())
  })

  describe('build', () => {
    let editor, builder, composer

    function initializeSpies (filePath, jobnames = [null], statusCode = 0) {
      editor = jasmine.createSpyObj('MockEditor', ['save', 'isModified'])
      spyOn(composer, 'resolveRootFilePath').andReturn(filePath)
      spyOn(werkzeug, 'getEditorDetails').andReturn({ editor, filePath })

      builder = jasmine.createSpyObj('MockBuilder', ['run', 'constructArgs', 'parseLogAndFdbFiles', 'getJobNamesFromMagic', 'getOutputDirectory'])
      builder.getJobNamesFromMagic.andReturn(jobnames)
      builder.getOutputDirectory.andReturn('')
      builder.run.andCallFake(() => {
        switch (statusCode) {
          case 0: { return Promise.resolve(statusCode) }
        }

        return Promise.reject(statusCode)
      })
      spyOn(latex.builderRegistry, 'getBuilder').andReturn(builder)
    }

    beforeEach(() => {
      composer = new Composer()
      spyOn(composer, 'showResult').andReturn()
      spyOn(composer, 'showError').andReturn()
    })

    it('does nothing for new, unsaved files', () => {
      initializeSpies(null)

      let result = 'aaaaaaaaaaaa'
      waitsForPromise(() => {
        return composer.build().then(r => { result = r })
      })

      runs(() => {
        expect(result).toBe(false)
        expect(composer.showResult).not.toHaveBeenCalled()
        expect(composer.showError).not.toHaveBeenCalled()
      })
    })

    it('does nothing for unsupported file extensions', () => {
      initializeSpies('foo.bar')
      latex.builderRegistry.getBuilder.andReturn(null)

      let result
      waitsForPromise(() => {
        return composer.build().then(r => { result = r })
      })

      runs(() => {
        expect(result).toBe(false)
        expect(composer.showResult).not.toHaveBeenCalled()
        expect(composer.showError).not.toHaveBeenCalled()
      })
    })

    it('saves the file before building, if modified', () => {
      initializeSpies('file.tex')
      editor.isModified.andReturn(true)

      builder.parseLogAndFdbFiles.andReturn({
        outputFilePath: 'file.pdf',
        messages: []
      })

      waitsForPromise(() => {
        return composer.build()
      })

      runs(() => {
        expect(editor.isModified).toHaveBeenCalled()
        expect(editor.save).toHaveBeenCalled()
      })
    })

    it('runs the build two times with multiple job names', () => {
      initializeSpies('file.tex', ['foo', 'bar'])

      builder.parseLogAndFdbFiles.andReturn({
        outputFilePath: 'file.pdf',
        messages: []
      })

      waitsForPromise(() => {
        return composer.build()
      })

      runs(() => {
        expect(builder.run.callCount).toBe(2)
      })
    })

    it('invokes `showResult` after a successful build, with expected log parsing result', () => {
      const result = {
        outputFilePath: 'file.pdf',
        messages: []
      }

      initializeSpies('file.tex')
      builder.parseLogAndFdbFiles.andReturn(result)

      waitsForPromise(() => {
        return composer.build()
      })

      runs(() => {
        expect(composer.showResult).toHaveBeenCalledWith(result)
      })
    })

    it('treats missing output file data in log file as an error', () => {
      initializeSpies('file.tex')

      builder.parseLogAndFdbFiles.andReturn({
        outputFilePath: null,
        messages: []
      })

      waitsForPromise(() => {
        return composer.build().catch(r => r)
      })

      runs(() => {
        expect(composer.showError).toHaveBeenCalled()
      })
    })

    it('treats missing result from parser as an error', () => {
      initializeSpies('file.tex')
      builder.parseLogAndFdbFiles.andReturn(null)

      waitsForPromise(() => {
        return composer.build().catch(r => r)
      })

      runs(() => {
        expect(composer.showError).toHaveBeenCalled()
      })
    })

    it('handles active item not being a text editor', () => {
      spyOn(atom.workspace, 'getActiveTextEditor').andReturn()
      spyOn(werkzeug, 'getEditorDetails').andCallThrough()

      waitsForPromise(() => {
        return composer.build().catch(r => r)
      })

      runs(() => {
        expect(werkzeug.getEditorDetails).toHaveBeenCalled()
      })
    })
  })

  describe('clean', () => {
    let fixturesPath, composer

    function initializeSpies (filePath, jobnames = [null]) {
      const builder = jasmine.createSpyObj('MockBuilder', ['parseFdbFile', 'getJobNamesFromMagic', 'getOutputDirectory'])

      const outputDirectory = atom.config.get('latex.outputDirectory')
      spyOn(werkzeug, 'getEditorDetails').andReturn({ filePath })
      spyOn(composer, 'resolveRootFilePath').andReturn(filePath)
      spyOn(composer, 'initializeBuild').andReturn({ rootFilePath: filePath, builder, jobnames })
      spyOn(composer, 'getGeneratedFileList').andCallFake((builder, rootFilePath, jobname) => {
        let { dir, name } = path.parse(rootFilePath)
        if (outputDirectory) {
          dir = path.resolve(dir, outputDirectory)
        }
        if (jobname) name = jobname
        return new Set([
          path.format({ dir, name, ext: '.log' }),
          path.format({ dir, name, ext: '.aux' })
        ])
      })
    }

    beforeEach(() => {
      composer = new Composer()
      fixturesPath = helpers.cloneFixtures()
      spyOn(fs, 'removeSync').andCallThrough()
      atom.config.set('latex.cleanPatterns', ['**/*.aux', '/_minted-{jobname}'])
    })

    it('deletes aux file but leaves log file when log file is not in cleanPatterns', () => {
      initializeSpies(path.join(fixturesPath, 'foo.tex'))

      waitsForPromise(() => {
        return composer.clean().catch(r => r)
      })

      runs(() => {
        expect(fs.removeSync).toHaveBeenCalledWith(path.join(fixturesPath, 'foo.aux'))
        expect(fs.removeSync).not.toHaveBeenCalledWith(path.join(fixturesPath, '_minted-foo'))
        expect(fs.removeSync).not.toHaveBeenCalledWith(path.join(fixturesPath, 'foo.log'))
      })
    })

    it('deletes aux file but leaves log file when log file is not in cleanPatterns with output directory', () => {
      const outdir = 'build'
      atom.config.set('latex.outputDirectory', outdir)
      initializeSpies(path.join(fixturesPath, 'foo.tex'))

      waitsForPromise(() => {
        return composer.clean().catch(r => r)
      })

      runs(() => {
        expect(fs.removeSync).toHaveBeenCalledWith(path.join(fixturesPath, outdir, 'foo.aux'))
        expect(fs.removeSync).not.toHaveBeenCalledWith(path.join(fixturesPath, '_minted-foo'))
        expect(fs.removeSync).not.toHaveBeenCalledWith(path.join(fixturesPath, outdir, 'foo.log'))
      })
    })

    it('deletes aux file but leaves log file when log file is not in cleanPatterns with relative output directory', () => {
      const outdir = path.join('..', 'build')
      atom.config.set('latex.outputDirectory', outdir)
      initializeSpies(path.join(fixturesPath, 'foo.tex'))

      waitsForPromise(() => {
        return composer.clean().catch(r => r)
      })

      runs(() => {
        expect(fs.removeSync).toHaveBeenCalledWith(path.join(fixturesPath, outdir, 'foo.aux'))
        expect(fs.removeSync).not.toHaveBeenCalledWith(path.join(fixturesPath, '_minted-foo'))
        expect(fs.removeSync).not.toHaveBeenCalledWith(path.join(fixturesPath, outdir, 'foo.log'))
      })
    })

    it('deletes aux file but leaves log file when log file is not in cleanPatterns with absolute output directory', () => {
      const rootDir = process.platform === 'win32' ? 'c:\\' : '/'
      const outdir = path.join(rootDir, 'build')
      atom.config.set('latex.outputDirectory', outdir)
      initializeSpies(path.join(fixturesPath, 'foo.tex'))

      waitsForPromise(() => {
        return composer.clean().catch(r => r)
      })

      runs(() => {
        expect(fs.removeSync).toHaveBeenCalledWith(path.join(outdir, 'foo.aux'))
        expect(fs.removeSync).not.toHaveBeenCalledWith(path.join(fixturesPath, '_minted-foo'))
        expect(fs.removeSync).not.toHaveBeenCalledWith(path.join(outdir, 'foo.log'))
      })
    })

    it('deletes aux files but leaves log files when log file is not in cleanPatterns with jobnames', () => {
      initializeSpies(path.join(fixturesPath, 'foo.tex'), ['bar', 'wibble'])

      waitsForPromise(() => {
        return composer.clean().catch(r => r)
      })

      runs(() => {
        expect(fs.removeSync).toHaveBeenCalledWith(path.join(fixturesPath, 'bar.aux'))
        expect(fs.removeSync).not.toHaveBeenCalledWith(path.join(fixturesPath, 'bar.log'))
        expect(fs.removeSync).not.toHaveBeenCalledWith(path.join(fixturesPath, '_minted-bar'))
        expect(fs.removeSync).toHaveBeenCalledWith(path.join(fixturesPath, 'wibble.aux'))
        expect(fs.removeSync).not.toHaveBeenCalledWith(path.join(fixturesPath, 'wibble.log'))
        expect(fs.removeSync).toHaveBeenCalledWith(path.join(fixturesPath, '_minted-wibble'))
      })
    })

    it('stops immediately if the file is not a TeX document', () => {
      const filePath = 'foo.bar'
      initializeSpies(filePath, [])

      waitsForPromise(() => {
        return composer.clean().catch(r => r)
      })

      runs(() => {
        expect(fs.removeSync).not.toHaveBeenCalled()
      })
    })
  })

  describe('shouldMoveResult', () => {
    let builder, composer
    const rootFilePath = '/wibble/gronk.tex'

    function initializeSpies (outputDirectory = '') {
      builder = { getOutputDirectory: () => outputDirectory }
      composer = new Composer()
    }

    it('should return false when using neither an output directory, nor the move option', () => {
      initializeSpies()
      atom.config.set('latex.moveResultToSourceDirectory', false)

      expect(composer.shouldMoveResult(builder, rootFilePath)).toBe(false)
    })

    it('should return false when not using an output directory, but using the move option', () => {
      initializeSpies()
      atom.config.set('latex.moveResultToSourceDirectory', true)

      expect(composer.shouldMoveResult(builder, rootFilePath)).toBe(false)
    })

    it('should return false when not using the move option, but using an output directory', () => {
      initializeSpies('baz')
      atom.config.set('latex.moveResultToSourceDirectory', false)

      expect(composer.shouldMoveResult(builder, rootFilePath)).toBe(false)
    })

    it('should return true when using both an output directory and the move option', () => {
      initializeSpies('baz')
      atom.config.set('latex.moveResultToSourceDirectory', true)

      expect(composer.shouldMoveResult(builder, rootFilePath)).toBe(true)
    })
  })

  describe('sync', () => {
    let composer

    beforeEach(() => {
      composer = new Composer()
    })

    it('silently does nothing when the current editor is transient', () => {
      spyOn(werkzeug, 'getEditorDetails').andReturn({ filePath: null })
      spyOn(composer, 'resolveOutputFilePath').andCallThrough()
      spyOn(latex.opener, 'open').andReturn(true)

      waitsForPromise(() => composer.sync())

      runs(() => {
        expect(composer.resolveOutputFilePath).not.toHaveBeenCalled()
        expect(latex.opener.open).not.toHaveBeenCalled()
      })
    })

    it('logs a warning and returns when an output file cannot be resolved', () => {
      spyOn(werkzeug, 'getEditorDetails').andReturn({ filePath: 'file.tex', lineNumber: 1 })
      spyOn(composer, 'resolveOutputFilePath').andReturn()
      spyOn(latex.opener, 'open').andReturn(true)
      spyOn(latex.log, 'warning').andCallThrough()

      waitsForPromise(() => composer.sync())

      runs(() => {
        expect(latex.log.warning).toHaveBeenCalled()
        expect(latex.opener.open).not.toHaveBeenCalled()
      })
    })

    it('launches the opener using editor metadata and resolved output file', () => {
      const filePath = 'file.tex'
      const lineNumber = 1
      const outputFilePath = 'file.pdf'
      spyOn(werkzeug, 'getEditorDetails').andReturn({ filePath, lineNumber })
      spyOn(composer, 'resolveOutputFilePath').andReturn(outputFilePath)

      spyOn(latex.opener, 'open').andReturn(true)

      waitsForPromise(() => composer.sync())

      runs(() => {
        expect(latex.opener.open).toHaveBeenCalledWith(outputFilePath, filePath, lineNumber)
      })
    })

    it('launches the opener using editor metadata and resolved output file with jobnames', () => {
      const filePath = 'file.tex'
      const lineNumber = 1
      const rootFilePath = filePath
      const builder = jasmine.createSpyObj('MockBuilder', ['run', 'constructArgs', 'parseLogAndFdbFiles', 'getJobNamesFromMagic'])
      const jobnames = ['foo', 'bar']

      spyOn(werkzeug, 'getEditorDetails').andReturn({ filePath, lineNumber })
      spyOn(composer, 'resolveOutputFilePath').andCallFake((builder, rootFilePath, jobname) => jobname + '.pdf')
      spyOn(composer, 'initializeBuild').andReturn({ rootFilePath, builder, jobnames })

      spyOn(latex.opener, 'open').andReturn(true)

      waitsForPromise(() => composer.sync())

      runs(() => {
        expect(latex.opener.open).toHaveBeenCalledWith('foo.pdf', filePath, lineNumber)
        expect(latex.opener.open).toHaveBeenCalledWith('bar.pdf', filePath, lineNumber)
      })
    })
  })
})
<commit_msg>Simplify absolute path in test<commit_after>/** @babel */

import helpers from './spec-helpers'
import fs from 'fs-plus'
import path from 'path'
import werkzeug from '../lib/werkzeug'
import Composer from '../lib/composer'

describe('Composer', () => {
  beforeEach(() => {
    waitsForPromise(() => helpers.activatePackages())
  })

  describe('build', () => {
    let editor, builder, composer

    function initializeSpies (filePath, jobnames = [null], statusCode = 0) {
      editor = jasmine.createSpyObj('MockEditor', ['save', 'isModified'])
      spyOn(composer, 'resolveRootFilePath').andReturn(filePath)
      spyOn(werkzeug, 'getEditorDetails').andReturn({ editor, filePath })

      builder = jasmine.createSpyObj('MockBuilder', ['run', 'constructArgs', 'parseLogAndFdbFiles', 'getJobNamesFromMagic', 'getOutputDirectory'])
      builder.getJobNamesFromMagic.andReturn(jobnames)
      builder.getOutputDirectory.andReturn('')
      builder.run.andCallFake(() => {
        switch (statusCode) {
          case 0: { return Promise.resolve(statusCode) }
        }

        return Promise.reject(statusCode)
      })
      spyOn(latex.builderRegistry, 'getBuilder').andReturn(builder)
    }

    beforeEach(() => {
      composer = new Composer()
      spyOn(composer, 'showResult').andReturn()
      spyOn(composer, 'showError').andReturn()
    })

    it('does nothing for new, unsaved files', () => {
      initializeSpies(null)

      let result = 'aaaaaaaaaaaa'
      waitsForPromise(() => {
        return composer.build().then(r => { result = r })
      })

      runs(() => {
        expect(result).toBe(false)
        expect(composer.showResult).not.toHaveBeenCalled()
        expect(composer.showError).not.toHaveBeenCalled()
      })
    })

    it('does nothing for unsupported file extensions', () => {
      initializeSpies('foo.bar')
      latex.builderRegistry.getBuilder.andReturn(null)

      let result
      waitsForPromise(() => {
        return composer.build().then(r => { result = r })
      })

      runs(() => {
        expect(result).toBe(false)
        expect(composer.showResult).not.toHaveBeenCalled()
        expect(composer.showError).not.toHaveBeenCalled()
      })
    })

    it('saves the file before building, if modified', () => {
      initializeSpies('file.tex')
      editor.isModified.andReturn(true)

      builder.parseLogAndFdbFiles.andReturn({
        outputFilePath: 'file.pdf',
        messages: []
      })

      waitsForPromise(() => {
        return composer.build()
      })

      runs(() => {
        expect(editor.isModified).toHaveBeenCalled()
        expect(editor.save).toHaveBeenCalled()
      })
    })

    it('runs the build two times with multiple job names', () => {
      initializeSpies('file.tex', ['foo', 'bar'])

      builder.parseLogAndFdbFiles.andReturn({
        outputFilePath: 'file.pdf',
        messages: []
      })

      waitsForPromise(() => {
        return composer.build()
      })

      runs(() => {
        expect(builder.run.callCount).toBe(2)
      })
    })

    it('invokes `showResult` after a successful build, with expected log parsing result', () => {
      const result = {
        outputFilePath: 'file.pdf',
        messages: []
      }

      initializeSpies('file.tex')
      builder.parseLogAndFdbFiles.andReturn(result)

      waitsForPromise(() => {
        return composer.build()
      })

      runs(() => {
        expect(composer.showResult).toHaveBeenCalledWith(result)
      })
    })

    it('treats missing output file data in log file as an error', () => {
      initializeSpies('file.tex')

      builder.parseLogAndFdbFiles.andReturn({
        outputFilePath: null,
        messages: []
      })

      waitsForPromise(() => {
        return composer.build().catch(r => r)
      })

      runs(() => {
        expect(composer.showError).toHaveBeenCalled()
      })
    })

    it('treats missing result from parser as an error', () => {
      initializeSpies('file.tex')
      builder.parseLogAndFdbFiles.andReturn(null)

      waitsForPromise(() => {
        return composer.build().catch(r => r)
      })

      runs(() => {
        expect(composer.showError).toHaveBeenCalled()
      })
    })

    it('handles active item not being a text editor', () => {
      spyOn(atom.workspace, 'getActiveTextEditor').andReturn()
      spyOn(werkzeug, 'getEditorDetails').andCallThrough()

      waitsForPromise(() => {
        return composer.build().catch(r => r)
      })

      runs(() => {
        expect(werkzeug.getEditorDetails).toHaveBeenCalled()
      })
    })
  })

  describe('clean', () => {
    let fixturesPath, composer

    function initializeSpies (filePath, jobnames = [null]) {
      const builder = jasmine.createSpyObj('MockBuilder', ['parseFdbFile', 'getJobNamesFromMagic', 'getOutputDirectory'])

      const outputDirectory = atom.config.get('latex.outputDirectory')
      spyOn(werkzeug, 'getEditorDetails').andReturn({ filePath })
      spyOn(composer, 'resolveRootFilePath').andReturn(filePath)
      spyOn(composer, 'initializeBuild').andReturn({ rootFilePath: filePath, builder, jobnames })
      spyOn(composer, 'getGeneratedFileList').andCallFake((builder, rootFilePath, jobname) => {
        let { dir, name } = path.parse(rootFilePath)
        if (outputDirectory) {
          dir = path.resolve(dir, outputDirectory)
        }
        if (jobname) name = jobname
        return new Set([
          path.format({ dir, name, ext: '.log' }),
          path.format({ dir, name, ext: '.aux' })
        ])
      })
    }

    beforeEach(() => {
      composer = new Composer()
      fixturesPath = helpers.cloneFixtures()
      spyOn(fs, 'removeSync').andCallThrough()
      atom.config.set('latex.cleanPatterns', ['**/*.aux', '/_minted-{jobname}'])
    })

    it('deletes aux file but leaves log file when log file is not in cleanPatterns', () => {
      initializeSpies(path.join(fixturesPath, 'foo.tex'))

      waitsForPromise(() => {
        return composer.clean().catch(r => r)
      })

      runs(() => {
        expect(fs.removeSync).toHaveBeenCalledWith(path.join(fixturesPath, 'foo.aux'))
        expect(fs.removeSync).not.toHaveBeenCalledWith(path.join(fixturesPath, '_minted-foo'))
        expect(fs.removeSync).not.toHaveBeenCalledWith(path.join(fixturesPath, 'foo.log'))
      })
    })

    it('deletes aux file but leaves log file when log file is not in cleanPatterns with output directory', () => {
      const outdir = 'build'
      atom.config.set('latex.outputDirectory', outdir)
      initializeSpies(path.join(fixturesPath, 'foo.tex'))

      waitsForPromise(() => {
        return composer.clean().catch(r => r)
      })

      runs(() => {
        expect(fs.removeSync).toHaveBeenCalledWith(path.join(fixturesPath, outdir, 'foo.aux'))
        expect(fs.removeSync).not.toHaveBeenCalledWith(path.join(fixturesPath, '_minted-foo'))
        expect(fs.removeSync).not.toHaveBeenCalledWith(path.join(fixturesPath, outdir, 'foo.log'))
      })
    })

    it('deletes aux file but leaves log file when log file is not in cleanPatterns with relative output directory', () => {
      const outdir = path.join('..', 'build')
      atom.config.set('latex.outputDirectory', outdir)
      initializeSpies(path.join(fixturesPath, 'foo.tex'))

      waitsForPromise(() => {
        return composer.clean().catch(r => r)
      })

      runs(() => {
        expect(fs.removeSync).toHaveBeenCalledWith(path.join(fixturesPath, outdir, 'foo.aux'))
        expect(fs.removeSync).not.toHaveBeenCalledWith(path.join(fixturesPath, '_minted-foo'))
        expect(fs.removeSync).not.toHaveBeenCalledWith(path.join(fixturesPath, outdir, 'foo.log'))
      })
    })

    it('deletes aux file but leaves log file when log file is not in cleanPatterns with absolute output directory', () => {
      const outdir = process.platform === 'win32' ? 'c:\\build' : '/build'
      atom.config.set('latex.outputDirectory', outdir)
      initializeSpies(path.join(fixturesPath, 'foo.tex'))

      waitsForPromise(() => {
        return composer.clean().catch(r => r)
      })

      runs(() => {
        expect(fs.removeSync).toHaveBeenCalledWith(path.join(outdir, 'foo.aux'))
        expect(fs.removeSync).not.toHaveBeenCalledWith(path.join(fixturesPath, '_minted-foo'))
        expect(fs.removeSync).not.toHaveBeenCalledWith(path.join(outdir, 'foo.log'))
      })
    })

    it('deletes aux files but leaves log files when log file is not in cleanPatterns with jobnames', () => {
      initializeSpies(path.join(fixturesPath, 'foo.tex'), ['bar', 'wibble'])

      waitsForPromise(() => {
        return composer.clean().catch(r => r)
      })

      runs(() => {
        expect(fs.removeSync).toHaveBeenCalledWith(path.join(fixturesPath, 'bar.aux'))
        expect(fs.removeSync).not.toHaveBeenCalledWith(path.join(fixturesPath, 'bar.log'))
        expect(fs.removeSync).not.toHaveBeenCalledWith(path.join(fixturesPath, '_minted-bar'))
        expect(fs.removeSync).toHaveBeenCalledWith(path.join(fixturesPath, 'wibble.aux'))
        expect(fs.removeSync).not.toHaveBeenCalledWith(path.join(fixturesPath, 'wibble.log'))
        expect(fs.removeSync).toHaveBeenCalledWith(path.join(fixturesPath, '_minted-wibble'))
      })
    })

    it('stops immediately if the file is not a TeX document', () => {
      const filePath = 'foo.bar'
      initializeSpies(filePath, [])

      waitsForPromise(() => {
        return composer.clean().catch(r => r)
      })

      runs(() => {
        expect(fs.removeSync).not.toHaveBeenCalled()
      })
    })
  })

  describe('shouldMoveResult', () => {
    let builder, composer
    const rootFilePath = '/wibble/gronk.tex'

    function initializeSpies (outputDirectory = '') {
      builder = { getOutputDirectory: () => outputDirectory }
      composer = new Composer()
    }

    it('should return false when using neither an output directory, nor the move option', () => {
      initializeSpies()
      atom.config.set('latex.moveResultToSourceDirectory', false)

      expect(composer.shouldMoveResult(builder, rootFilePath)).toBe(false)
    })

    it('should return false when not using an output directory, but using the move option', () => {
      initializeSpies()
      atom.config.set('latex.moveResultToSourceDirectory', true)

      expect(composer.shouldMoveResult(builder, rootFilePath)).toBe(false)
    })

    it('should return false when not using the move option, but using an output directory', () => {
      initializeSpies('baz')
      atom.config.set('latex.moveResultToSourceDirectory', false)

      expect(composer.shouldMoveResult(builder, rootFilePath)).toBe(false)
    })

    it('should return true when using both an output directory and the move option', () => {
      initializeSpies('baz')
      atom.config.set('latex.moveResultToSourceDirectory', true)

      expect(composer.shouldMoveResult(builder, rootFilePath)).toBe(true)
    })
  })

  describe('sync', () => {
    let composer

    beforeEach(() => {
      composer = new Composer()
    })

    it('silently does nothing when the current editor is transient', () => {
      spyOn(werkzeug, 'getEditorDetails').andReturn({ filePath: null })
      spyOn(composer, 'resolveOutputFilePath').andCallThrough()
      spyOn(latex.opener, 'open').andReturn(true)

      waitsForPromise(() => composer.sync())

      runs(() => {
        expect(composer.resolveOutputFilePath).not.toHaveBeenCalled()
        expect(latex.opener.open).not.toHaveBeenCalled()
      })
    })

    it('logs a warning and returns when an output file cannot be resolved', () => {
      spyOn(werkzeug, 'getEditorDetails').andReturn({ filePath: 'file.tex', lineNumber: 1 })
      spyOn(composer, 'resolveOutputFilePath').andReturn()
      spyOn(latex.opener, 'open').andReturn(true)
      spyOn(latex.log, 'warning').andCallThrough()

      waitsForPromise(() => composer.sync())

      runs(() => {
        expect(latex.log.warning).toHaveBeenCalled()
        expect(latex.opener.open).not.toHaveBeenCalled()
      })
    })

    it('launches the opener using editor metadata and resolved output file', () => {
      const filePath = 'file.tex'
      const lineNumber = 1
      const outputFilePath = 'file.pdf'
      spyOn(werkzeug, 'getEditorDetails').andReturn({ filePath, lineNumber })
      spyOn(composer, 'resolveOutputFilePath').andReturn(outputFilePath)

      spyOn(latex.opener, 'open').andReturn(true)

      waitsForPromise(() => composer.sync())

      runs(() => {
        expect(latex.opener.open).toHaveBeenCalledWith(outputFilePath, filePath, lineNumber)
      })
    })

    it('launches the opener using editor metadata and resolved output file with jobnames', () => {
      const filePath = 'file.tex'
      const lineNumber = 1
      const rootFilePath = filePath
      const builder = jasmine.createSpyObj('MockBuilder', ['run', 'constructArgs', 'parseLogAndFdbFiles', 'getJobNamesFromMagic'])
      const jobnames = ['foo', 'bar']

      spyOn(werkzeug, 'getEditorDetails').andReturn({ filePath, lineNumber })
      spyOn(composer, 'resolveOutputFilePath').andCallFake((builder, rootFilePath, jobname) => jobname + '.pdf')
      spyOn(composer, 'initializeBuild').andReturn({ rootFilePath, builder, jobnames })

      spyOn(latex.opener, 'open').andReturn(true)

      waitsForPromise(() => composer.sync())

      runs(() => {
        expect(latex.opener.open).toHaveBeenCalledWith('foo.pdf', filePath, lineNumber)
        expect(latex.opener.open).toHaveBeenCalledWith('bar.pdf', filePath, lineNumber)
      })
    })
  })
})
<|endoftext|>


<commit_before>/*
 * Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * WordPress dependencies
 */
import { __ } from '@wordpress/i18n';
import { format } from '@wordpress/date';

/**
 * External dependencies
 */
import moment from 'moment-timezone';

/**
 * Internal dependencies
 */
import {
  DEFAULT_DATE_FORMATTING,
  getDateFormattedFromWordPressToMoment,
  getTimeFromNow,
  isToday,
  isYesterday,
} from './';

// Returns hours since date if date passed in matches today
// Returns yesterday if date passed in is yesterday
// Returns formatted date + time by default
/**
 * @summary                        Formats a date to display relative to time passed since date.
 * If date to display is < 1 day ago it will display rounded time since date using timezone.
 * If date to display matches yesterday's date it will display "yesterday".
 * Otherwise date will come back formatted by dateFormatting.dateFormat (no time).
 * @param {Date} date              Date to format according to how much time or how many days have passed since date
 * If date is not an instance of moment when passed in it will create a moment from it.
 * @param {Object} dateFormatting  Object responsible for relevant date formatting.
 * Should contain dateFormat, timezone, gmtOffset, and timeFormat - all strings.
 * @return {string}                Displayable relative date string
 */

export function getTimeSensitiveDisplayDate(
  date,
  dateFormatting = DEFAULT_DATE_FORMATTING
) {
  if (!date) {
    return '';
  }
  // Here we need to make sure we use parseZone to honor existing timezone data
  // https://momentjs.com/guides/#/parsing/
  const displayDate = moment.isMoment(date) ? date : moment.parseZone(date);

  if (isToday(displayDate)) {
    return getTimeFromNow(displayDate, dateFormatting);
  } else if (isYesterday(displayDate)) {
    return __('yesterday', 'web-stories');
  }

  return (
    getDateFormattedFromWordPressToMoment(date, dateFormatting.dateFormat) ||
    format(dateFormatting?.dateFormat, date)
  );
}
<commit_msg>dateFormat will exist no need to check for it<commit_after>/*
 * Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * WordPress dependencies
 */
import { __ } from '@wordpress/i18n';
import { format } from '@wordpress/date';

/**
 * External dependencies
 */
import moment from 'moment-timezone';

/**
 * Internal dependencies
 */
import {
  DEFAULT_DATE_FORMATTING,
  getDateFormattedFromWordPressToMoment,
  getTimeFromNow,
  isToday,
  isYesterday,
} from './';

// Returns hours since date if date passed in matches today
// Returns yesterday if date passed in is yesterday
// Returns formatted date + time by default
/**
 * @summary                        Formats a date to display relative to time passed since date.
 * If date to display is < 1 day ago it will display rounded time since date using timezone.
 * If date to display matches yesterday's date it will display "yesterday".
 * Otherwise date will come back formatted by dateFormatting.dateFormat (no time).
 * @param {Date} date              Date to format according to how much time or how many days have passed since date
 * If date is not an instance of moment when passed in it will create a moment from it.
 * @param {Object} dateFormatting  Object responsible for relevant date formatting.
 * Should contain dateFormat, timezone, gmtOffset, and timeFormat - all strings.
 * @return {string}                Displayable relative date string
 */

export function getTimeSensitiveDisplayDate(
  date,
  dateFormatting = DEFAULT_DATE_FORMATTING
) {
  if (!date) {
    return '';
  }
  // Here we need to make sure we use parseZone to honor existing timezone data
  // https://momentjs.com/guides/#/parsing/
  const displayDate = moment.isMoment(date) ? date : moment.parseZone(date);

  if (isToday(displayDate)) {
    return getTimeFromNow(displayDate, dateFormatting);
  } else if (isYesterday(displayDate)) {
    return __('yesterday', 'web-stories');
  }

  return (
    getDateFormattedFromWordPressToMoment(date, dateFormatting.dateFormat) ||
    format(dateFormatting.dateFormat, date)
  );
}
<|endoftext|>