<commit_before>function defineShared(_, async, hooks, requirejs) {
  if (requirejs == undefined) requirejs = require;

  return function (exports) {
    /* Ugly hack. Remove? */
    hooks.plugins = exports;

    exports.ensure = function (cb) {
      if (!exports.loaded) {
        exports.reload(function () {
          exports.loaded = true;
          cb();
        });
      } else {
        cb();
      }
    };

    exports.loadModule = function(path, cb) {
      if (path == 'ep_carabiner/static/js/shared') {
        cb(exports);
      } else {
        hooks.aCallFirst("loadModule", {path: path}, function (err, res) {
          if (err) {
            console.warn("Error loading module: " + path + "\n" + err.toString());
          } else {
            cb(res[0]);
          }
        })
      }
    };

    exports.loadNodeModule = function(hook_name, args, cb) {
      var mod = [];
      try {
        mod = [require(args.path)];
      } catch (e) {
        console.warn("Error loading CommonJS module: " + args.path + "\n" + e.toString());
      }
      cb(mod);
    };

    exports.loadRequireJSModule = function(hook_name, args, cb) {
      requirejs([args.path], function (mod) {
        cb([mod]);
      });
    }

    exports.loadFn = function(path, hookName, cb) {
      var functionName
        , parts = path.split(":");

      // on windows: C:\foo\bar:xyz
      if (parts[0].length == 1) {
        if (parts.length == 3) {
          functionName = parts.pop();
        }
        path = parts.join(":");
      } else {
        path = parts[0];
        functionName = parts[1];
      }

      exports.loadModule(path, function (fn) {
        functionName = functionName ? functionName : hookName;

        _.each(functionName.split("."), function (name) {
          fn = fn[name];
        });
        cb(null, fn);
      });
    };

    exports.extractHooks = function(parts, hook_set_name) {
      var hooks = {};

      _.each(parts, function (part) {
        if (part[hook_set_name] != undefined) {
          for (var hook_name in part[hook_set_name]) {
            if (hooks[hook_name] === undefined) hooks[hook_name] = [];

            var hook_fn_name = part[hook_set_name][hook_name];

            hooks[hook_name].push({
              "hook_set_name": hook_set_name,
              "hook_name": hook_name,
              "hook_fn_name": hook_fn_name,
              "part_full_name": part.full_name
            });
          }
        }
      });

      return hooks;
    };

    exports.loadHook = function(hook, cb) {
      exports.loadFn(hook.hook_fn_name, hook.hook_name, function (err, hook_fn) {
        if (hook_fn) {
          hook.hook_fn = hook_fn;
        } else {
          hook.error = err;
          console.error("Failed to load '" + hook.hook_fn_name + "' for '" + hook.part_full_name + "/" + hook.hook_set_name + "/" + hook.hook_name + ":" + err.toString());
        }
        cb();
      });
    };

    exports.loadHooks = function(hooks, cb) {
      async.each(Object.keys(hooks), function (hook_name, cb) {
        async.each(hooks[hook_name], exports.loadHook.bind(exports), cb);
      },
      cb);
    };

    exports.formatPlugins = function () {
      return _.keys(exports.plugins).join(", ");
    };

    exports.formatParts = function (format, indent) {
      if (format == undefined) format = 'html';

      var parts = _.map(exports.parts, function (part) { return part.full_name; });
      if (format == 'text') {
        var parts = _.map(parts, function (part) { return indent + part; });
        return parts.join("\n");
      } else if (format == 'html') {
        var parts = _.map(parts, function (part) { return "
" + indent + part + "
"; });
        return "
" + parts.join("\n") + "
";
      }
    };

    exports.formatHooks = function (hook_set_name, format, indent) {
      if (format == undefined) format = 'html';
      if (indent == undefined) indent = '';

      var res = [];
      var hooks = exports[hook_set_name || "hooks"];

      _.chain(hooks).keys().forEach(function (hook_name) {
        _.forEach(hooks[hook_name], function (hook) {
          if (format == 'text') {
            res.push(indent + hook.hook_name + " -> " + hook.hook_fn_name + " from " + hook.part_full_name);
          } else if (format == 'html') {
            res.push("
" + indent + hook.hook_name + "

" + hook.hook_fn_name + " from " + hook.part_full_name + "
");
          }
        });
      });
      if (format == 'text') {
       return res.join("\n");
      } else if (format == 'html') {
        return "
" + res.join("\n") + "
";
      }
    };
  };
};

if (typeof(define) != 'undefined' && define.amd != undefined && typeof(exports) == 'undefined') {
    define(["underscore", "async", 'ep_carabiner/static/js/hooks'], defineShared);
} else {
    module.exports = defineShared(require("underscore"), require("async"), require('ep_carabiner/static/js/hooks'), require('requirejs'));
}
<commit_msg>Better error handling<commit_after>function defineShared(_, async, hooks, requirejs) {
  if (requirejs == undefined) requirejs = require;

  return function (exports) {
    /* Ugly hack. Remove? */
    hooks.plugins = exports;

    exports.ensure = function (cb) {
      if (!exports.loaded) {
        exports.reload(function () {
          exports.loaded = true;
          cb();
        });
      } else {
        cb();
      }
    };

    exports.loadModule = function(path, cb) {
      if (path == 'ep_carabiner/static/js/shared') {
        cb(null, exports);
      } else {
        var args = {path: path, errors: {}};
        hooks.aCallFirst("loadModule", args, function (loaderError, res) {
          if (loaderError) {
            args.errors.loaderError = loaderError;
          }
          if (res.length == 0) {
            var error = {
              errors: args.errors,
              path: path,
              toString: function () {
                var self = this;
                var modules = Object.keys(self.errors);
                modules.sort();
                return ("Error loading module: " + self.path + "\n" +
                  modules.map(function(module) {
                    return "  " + module + ": " + self.errors[module];
                  }).join("\n"));
              }
            };
	    cb(error);
          } else {
            cb(null, res[0]);
          }
        })
      }
    };

    exports.loadNodeModule = function(hook_name, args, cb) {
      var mod = [];
      try {
        mod = [require(args.path)];
      } catch (error) {
        args.errors.loadNodeModule = error;
      }
      cb(mod);
    };

    exports.loadRequireJSModule = function(hook_name, args, cb) {
      requirejs([args.path], function (mod) {
        cb([mod]);
      }, function (error) {
        args.errors.loadRequireJSModule = error;
        cb([]);
      });
    }

    exports.loadFn = function(path, hookName, cb) {
      var functionName
        , parts = path.split(":");

      // on windows: C:\foo\bar:xyz
      if (parts[0].length == 1) {
        if (parts.length == 3) {
          functionName = parts.pop();
        }
        path = parts.join(":");
      } else {
        path = parts[0];
        functionName = parts[1];
      }

      exports.loadModule(path, function (err, fn) {
        if (err) {
          cb(err);
        } else {
          functionName = functionName ? functionName : hookName;

          _.each(functionName.split("."), function (name) {
            fn = fn[name];
          });
          cb(null, fn);
        }
      });
    };

    exports.extractHooks = function(parts, hook_set_name) {
      var hooks = {};

      _.each(parts, function (part) {
        if (part[hook_set_name] != undefined) {
          for (var hook_name in part[hook_set_name]) {
            if (hooks[hook_name] === undefined) hooks[hook_name] = [];

            var hook_fn_name = part[hook_set_name][hook_name];

            hooks[hook_name].push({
              "hook_set_name": hook_set_name,
              "hook_name": hook_name,
              "hook_fn_name": hook_fn_name,
              "part_full_name": part.full_name
            });
          }
        }
      });

      return hooks;
    };

    exports.loadHook = function(hook, cb) {
      exports.loadFn(hook.hook_fn_name, hook.hook_name, function (err, hook_fn) {
        if (hook_fn) {
          hook.hook_fn = hook_fn;
        } else {
          hook.error = err;
          console.error("Failed to load '" + hook.hook_fn_name + "' for '" + hook.part_full_name + "/" + hook.hook_set_name + "/" + hook.hook_name + ":" + err.toString());
        }
        cb();
      });
    };

    exports.loadHooks = function(hooks, cb) {
      async.each(Object.keys(hooks), function (hook_name, cb) {
        async.each(hooks[hook_name], exports.loadHook.bind(exports), cb);
      },
      cb);
    };

    exports.formatPlugins = function () {
      return _.keys(exports.plugins).join(", ");
    };

    exports.formatParts = function (format, indent) {
      if (format == undefined) format = 'html';

      var parts = _.map(exports.parts, function (part) { return part.full_name; });
      if (format == 'text') {
        var parts = _.map(parts, function (part) { return indent + part; });
        return parts.join("\n");
      } else if (format == 'html') {
        var parts = _.map(parts, function (part) { return "
" + indent + part + "
"; });
        return "
" + parts.join("\n") + "
";
      }
    };

    exports.formatHooks = function (hook_set_name, format, indent) {
      if (format == undefined) format = 'html';
      if (indent == undefined) indent = '';

      var res = [];
      var hooks = exports[hook_set_name || "hooks"];

      _.chain(hooks).keys().forEach(function (hook_name) {
        _.forEach(hooks[hook_name], function (hook) {
          if (format == 'text') {
            res.push(indent + hook.hook_name + " -> " + hook.hook_fn_name + " from " + hook.part_full_name);
          } else if (format == 'html') {
            res.push("
" + indent + hook.hook_name + "

" + hook.hook_fn_name + " from " + hook.part_full_name + "
");
          }
        });
      });
      if (format == 'text') {
       return res.join("\n");
      } else if (format == 'html') {
        return "
" + res.join("\n") + "
";
      }
    };
  };
};

if (typeof(define) != 'undefined' && define.amd != undefined && typeof(exports) == 'undefined') {
    define(["underscore", "async", 'ep_carabiner/static/js/hooks'], defineShared);
} else {
    module.exports = defineShared(require("underscore"), require("async"), require('ep_carabiner/static/js/hooks'), require('requirejs'));
}
<|endoftext|>


<commit_before>describe('xml', function () {

  var xmlString = '<tests><test id="1"/></tests>',
      DOMParser, ActiveXObject, win;

  beforeEach(module('xml'));

  describe('manual parsing', function () {

    beforeEach(inject(function ($window) {
      win = $window;
      DOMParser = win.DOMParser;
      ActiveXObject = win.ActiveXObject;
    }));

    afterEach(function () {
      win.DOMParser = DOMParser;
      win.ActiveXObject = ActiveXObject;
    });

    describe('xmlFilter', function () {

      var filter, parser;

      beforeEach(inject(function (xmlParser, xmlFilter) {
        filter = xmlFilter;
        parser = xmlParser;
      }));

      it('will use the xmlParser.parse() method', function () {
        spyOn(parser, 'parse');
        filter(xmlString);
        expect(parser.parse).toHaveBeenCalledWith(xmlString);
      });

      it('will return a ng.element object', function () {
        var returnValue;
        spyOn(angular, 'element').andReturn('ng.xml.element');
        returnValue = filter(xmlString);
        expect(returnValue).toBe('ng.xml.element');
        angular.element.andCallThrough();
      });

      it('should integrate as expected', function () {
        var xml = filter(xmlString);
        expect(xml.find('test').length).toBe(1);
      });

    });

    describe('XMLParser', function () {

      describe('DOMParser', function () {

        var DOMParser;

        beforeEach(function () {
          DOMParser = jasmine.createSpy('DOMParser');
          DOMParser.prototype = jasmine.createSpyObj('prototype', ['parseFromString']);
          win.DOMParser = DOMParser;
          win.ActiveXObject = null;
        });

        it('will use the DOMParser class when it is available', inject(function (xmlParser) {
          expect(DOMParser).toHaveBeenCalled();
          xmlParser.parse(xmlString);
          expect(DOMParser.prototype.parseFromString).toHaveBeenCalled();
        }));

      });

      describe('ActiveXObject', function () {

        var ActiveXObject;

        beforeEach(function () {
          ActiveXObject = jasmine.createSpy('ActiveXObject');
          ActiveXObject.prototype = jasmine.createSpyObj('prototype', ['loadXML']);
          win.ActiveXObject = ActiveXObject;
          win.DOMParser = null;
        });

        describe('successful parsing', function () {

          beforeEach(function () {
            ActiveXObject.prototype.parseError = {errorCode: 0};
          });

          it('will use the ActiveXObject class when DOMParser is not available', inject(function (xmlParser) {
            expect(ActiveXObject).toHaveBeenCalled();
            xmlParser.parse(xmlString);
            expect(ActiveXObject.prototype.loadXML).toHaveBeenCalled();
          }));

        });

        describe('unsuccessful parsing', function () {

          beforeEach(function () {
            ActiveXObject.prototype.parseError = {errorCode: 1, reason: 'mung'};
          });

          it('will throw an error when there is a badly formed XML string', inject(function (xmlParser) {
            expect(function () {
              xmlParser.parse(xmlString);
            }).toThrow();
          }));

        });

      });

    });

  });

  describe('automatic parsing', function () {

    describe('httpInterceptor', function () {

      var responseHeaders, xmlFilter, $xmlHttpInterceptor;

      beforeEach(module(function ($provide) {
        responseHeaders = jasmine.createSpy('response.headers').andReturn('application/xml');
        xmlFilter = jasmine.createSpy('xmlFilter');
        $provide.value('xmlFilter', xmlFilter);
      }));

      beforeEach(inject(function (xmlHttpInterceptor) {
        $xmlHttpInterceptor = xmlHttpInterceptor;
      }));

      function respond() {
        $xmlHttpInterceptor.response({
          data: xmlString,
          headers: responseHeaders
        });
      }

      it('will check for the content-type', function () {
        respond();
        expect(responseHeaders).toHaveBeenCalledWith('content-type');
      });

      it('will return a ng.element object', function () {
        respond();
        expect(xmlFilter).toHaveBeenCalled();
      });

      it('will also work with the text/xml content-type', function () {
        responseHeaders.andReturn('text/xml');
        respond();
        expect(xmlFilter).toHaveBeenCalled();
      });

      it('will also work when then content-type has extra parameters', function () {
        responseHeaders.andReturn('application/xml, charset=UTF-8');
        respond();
        expect(xmlFilter).toHaveBeenCalled();
      });

      it('will only act on a XML response', function () {
        responseHeaders.andReturn('text/html');
        respond();
        expect(xmlFilter).not.toHaveBeenCalled();
      });

    });

  });

});

<commit_msg>Tidied the unit tests.<commit_after>describe('xml', function () {

  var xmlString = '<tests><test id="1"/></tests>';

  beforeEach(module('xml'));

  describe('xmlFilter', function () {

    var filter, parser;

    beforeEach(inject(function (xmlParser, xmlFilter) {
      filter = xmlFilter;
      parser = xmlParser;
    }));

    it('will use the xmlParser.parse() method', function () {
      spyOn(parser, 'parse');
      filter(xmlString);
      expect(parser.parse).toHaveBeenCalledWith(xmlString);
    });

    it('will return a ng.element object', function () {
      var returnValue;
      spyOn(angular, 'element').andReturn('ng.xml.element');
      returnValue = filter(xmlString);
      expect(returnValue).toBe('ng.xml.element');
      angular.element.andCallThrough();
    });

    it('should integrate as expected', function () {
      var xml = filter(xmlString);
      expect(xml.find('test').length).toBe(1);
    });

  });

  describe('XMLParser', function () {

    var DOMParser, ActiveXObject, win;

    beforeEach(inject(function ($window) {
      win = $window;
      DOMParser = win.DOMParser;
      ActiveXObject = win.ActiveXObject;
    }));

    afterEach(function () {
      win.DOMParser = DOMParser;
      win.ActiveXObject = ActiveXObject;
    });

    describe('DOMParser', function () {

      var DOMParser;

      beforeEach(function () {
        DOMParser = jasmine.createSpy('DOMParser');
        DOMParser.prototype = jasmine.createSpyObj('prototype', ['parseFromString']);
        win.DOMParser = DOMParser;
        win.ActiveXObject = null;
      });

      it('will use the DOMParser class when it is available', inject(function (xmlParser) {
        expect(DOMParser).toHaveBeenCalled();
        xmlParser.parse(xmlString);
        expect(DOMParser.prototype.parseFromString).toHaveBeenCalled();
      }));

    });

    describe('ActiveXObject', function () {

      var ActiveXObject;

      beforeEach(function () {
        ActiveXObject = jasmine.createSpy('ActiveXObject');
        ActiveXObject.prototype = jasmine.createSpyObj('prototype', ['loadXML']);
        win.ActiveXObject = ActiveXObject;
        win.DOMParser = null;
      });

      describe('successful parsing', function () {

        beforeEach(function () {
          ActiveXObject.prototype.parseError = {errorCode: 0};
        });

        it('will use the ActiveXObject class when DOMParser is not available', inject(function (xmlParser) {
          expect(ActiveXObject).toHaveBeenCalled();
          xmlParser.parse(xmlString);
          expect(ActiveXObject.prototype.loadXML).toHaveBeenCalled();
        }));

      });

      describe('unsuccessful parsing', function () {

        beforeEach(function () {
          ActiveXObject.prototype.parseError = {errorCode: 1, reason: 'mung'};
        });

        it('will throw an error when there is a badly formed XML string', inject(function (xmlParser) {
          expect(function () {
            xmlParser.parse(xmlString);
          }).toThrow();
        }));

      });

    });

  });

  describe('httpInterceptor', function () {

    var responseHeaders, xmlFilter, $xmlHttpInterceptor;

    beforeEach(module(function ($provide) {
      responseHeaders = jasmine.createSpy('response.headers').andReturn('application/xml');
      xmlFilter = jasmine.createSpy('xmlFilter');
      $provide.value('xmlFilter', xmlFilter);
    }));

    beforeEach(inject(function (xmlHttpInterceptor) {
      $xmlHttpInterceptor = xmlHttpInterceptor;
    }));

    function respond() {
      $xmlHttpInterceptor.response({
        data: xmlString,
        headers: responseHeaders
      });
    }

    it('will check for the content-type', function () {
      respond();
      expect(responseHeaders).toHaveBeenCalledWith('content-type');
    });

    it('will return a ng.element object', function () {
      respond();
      expect(xmlFilter).toHaveBeenCalled();
    });

    it('will also work with the text/xml content-type', function () {
      responseHeaders.andReturn('text/xml');
      respond();
      expect(xmlFilter).toHaveBeenCalled();
    });

    it('will also work when then content-type has extra parameters', function () {
      responseHeaders.andReturn('application/xml, charset=UTF-8');
      respond();
      expect(xmlFilter).toHaveBeenCalled();
    });

    it('will only act on a XML response', function () {
      responseHeaders.andReturn('text/html');
      respond();
      expect(xmlFilter).not.toHaveBeenCalled();
    });

  });

});

<|endoftext|>


<commit_before><commit_msg>Automatically built.<commit_after><|endoftext|>


<commit_before>/**
  Analytics for TVOS app.  Ultimately this should mirror the Player's analytics
  plugin, but for MVP, we'll hand-roll something simple, with a lot of
  hard-coded parameters.
**/
function TVOSAnalytics(item) {
  var _self = this,
    serverURL = "jwpltx.com",
    apiVersion = "v1",
    bucketName = "jwplayer6",
    analyticsToken = CONFIG['analyticsToken'],
    embedId = _genId(12),
    lastTime = 0,
    lastPingSent;

  /** @const */ var PARAM_CHECKSUM = 'h';

  //sent with all events
  /** @const */ var PARAM_TRACKER_VERSION     = 'tv';
  /** @const */ var PARAM_NONCE               = 'n';
  /** @const */ var PARAM_ANALYTICS_TOKEN     = 'aid';
  /** @const */ var PARAM_EVENT_TYPE          = 'e';
  /** @const */ var PARAM_IFRAME              = 'i';
  /** @const */ var PARAM_IFRAME_DEPTH        = 'ifd';
  /** @const */ var PARAM_PLAYER_VERSION      = 'pv';
  /** @const */ var PARAM_PAGE_URL            = 'pu';
  /** @const */ var PARAM_PAGE_TITLE          = 'pt';
  /** @const */ var PARAM_SDK_PLATFORM        = 'sdk';
  /** @const */ var PARAM_AUTOSTART           = 'd';
  /** @const */ var PARAM_AD_BLOCK            = 'eb';
  /** @const */ var PARAM_EMBED_ID            = 'emi';
  /** @const */ var PARAM_RENDERING_MODE      = 'm';
  /** @const */ var PARAM_MEDIA_URL           = 'mu';
  /** @const */ var PARAM_PLAYER_HOSTING      = 'ph';
  /** @const */ var PARAM_ITEM_ID             = 'pli';
  /** @const */ var PARAM_PLAYER_SIZE         = 'ps';
  /** @const */ var PARAM_MOBILE_SDK_VERSION  = 'sv';
  /** @const */ var PARAM_TITLE               = 't';

  //mobile SDK specific
  /** @const */ var PARAM_MOBILE_APP_BUNDLEID = 'bi';
  /** @const */ var PARAM_MOBILE_APP_NAME     = 'an';
  /** @const */ var PARAM_MOBILE_DEVICE_ID    = 'did';
  /** @const */ var PARAM_PLAYER_DEVICE_MODEL = 'dm';

  // Tracking Parameter Keys
  /** @const */ var PARAM_EDITION               = 'ed';
  /** @const */ var PARAM_TIME_INTERVAL         = 'ti';
  /** @const */ var PARAM_TIME_WATCHED          = 'pw';
  /** @const */ var PARAM_VIDEO_SIZE            = 'vs';
  /** @const */ var PARAM_PLAYER_WIDTH          = 'wd';
  /** @const */ var PARAM_PLAYER_HEIGHT         = 'pl';
  /** @const */ var PARAM_VIDEO_LENGTH          = 'l';
  /** @const */ var PARAM_QUANTILES             = 'q';
  /** @const */ var PARAM_MEDIA_ID              = 'id';
  /** @const */ var PARAM_FLASH_VERSION         = 'fv';
  /** @const */ var PARAM_SETUP_TIME            = 'st';
  /** @const */ var PARAM_FIRST_FRAME           = 'ff';
  /** @const */ var PARAM_PROVIDER              = 'pp';
  /** @const */ var PARAM_VISUAL_PLAYLIST       = 'vp';
  /** @const */ var PARAM_POSTER_IMAGE          = 'po';
  /** @const */ var PARAM_SHARING               = 's';
  /** @const */ var PARAM_RELATED               = 'r';
  /** @const */ var PARAM_SKIN_NAME             = 'sn';
  /** @const */ var PARAM_CASTING_BLOCK         = 'cb';
  /** @const */ var PARAM_GA_BLOCK              = 'ga';
  /** @const */ var PARAM_PLAY_REASON           = 'pr';
  /** @const */ var PARAM_DASHBOARD_CONFIG_KEY  = 'pid';
  /** @const */ var PARAM_DISPLAY_DESCRIPTION   = 'dd';
  /** @const */ var PARAM_CHROMELESS_PLAYER     = 'cp';
  /** @const */ var PARAM_ADVERTISING_BLOCK     = 'ab';

  // Tracking Events
  /** @const */ var EVENT_VIDEO_EMBED  = 'e';
  /** @const */ var EVENT_VIDEO_PLAY   = 's';
  /** @const */ var EVENT_TIME_WATCHED = 't';

  function _genId(len) {
      return new Array(len+1).join((Math.random().toString(36)+'00000000000000000').slice(2, 18)).slice(0, len);
  }

  function _hashParam(s) {
    s = decodeURIComponent(s);
    var h = 0;

    for (var i = 0; i < s.length; i++) {
      var c = s.charCodeAt(i);
      h = ((h << 5) - h) + c;
      h &= h;
    }

    return `h=${h}`;
  }

  /** Generate ping URL and send it **/
  function _sendEvent(event, data) {
    var parameters = {};
    parameters[PARAM_NONCE] = Math.random().toFixed(16).substr(2, 16);
    parameters[PARAM_ANALYTICS_TOKEN] = analyticsToken;
    parameters[PARAM_EVENT_TYPE] = event;
    parameters[PARAM_SDK_PLATFORM] = 4; // 4 = AppleTV
    parameters[PARAM_MOBILE_SDK_VERSION] = "tvos-0.1";
    parameters[PARAM_TRACKER_VERSION] = "";
    parameters[PARAM_IFRAME] = "";
    parameters[PARAM_IFRAME_DEPTH] = 0;
    parameters[PARAM_PLAYER_VERSION] = "";
    parameters[PARAM_PAGE_URL] = "";
    parameters[PARAM_PAGE_TITLE] = "";
    parameters[PARAM_AUTOSTART] = 1;
    parameters[PARAM_AD_BLOCK] = 0;
    parameters[PARAM_EMBED_ID] = embedId;
    parameters[PARAM_RENDERING_MODE] = "";
    parameters[PARAM_PLAYER_HOSTING] = 0;
    parameters[PARAM_PLAYER_SIZE] = "";
    parameters = extend(parameters, data);

    /** Generate list of key/value URL parameter pairs **/
    var trackingArgs = [];
    for (var key in parameters) {
      var value = encodeURIComponent(parameters[key]);
      trackingArgs.push(`${key}=${value}`);
    }

    /** Generate ping request argument string **/
    var trackingArgsStr = trackingArgs.join("&");

    /** MD5 hash for server-side integrity check **/
    var hash = _hashParam(trackingArgsStr);

    /** Compose ping URL **/
    var trackerURL = `https://${serverURL}/${apiVersion}/${bucketName}/ping.gif?${hash}&${trackingArgsStr}`;

    /** Make a request to the analytics endpoint. No need to wait for a result **/
    var xhr = new XMLHttpRequest();
    xhr.open("GET", trackerURL);
    xhr.send();

    /** Record the last time a ping was sent **/
    lastPingSent = new Date();

    return trackerURL;

  }

  function _mediaParams() {
    var params = {};
    params[PARAM_MEDIA_URL] = item.url;
    params[PARAM_ITEM_ID] = item.externalID;
    params[PARAM_TITLE] = item.title;
    return params;
  }

  _self._sendStart = function() {
    var evt = _mediaParams();
    evt[PARAM_VIDEO_LENGTH] = "";
    evt[PARAM_QUANTILES] = "";
    evt[PARAM_VIDEO_SIZE] = "";
    evt[PARAM_FIRST_FRAME] = "";
    evt[PARAM_PROVIDER] = "";
    evt[PARAM_PLAY_REASON] = "";

    _sendEvent(EVENT_VIDEO_PLAY, evt);
  };

  _self._sendEmbed = function() {
    var evt = {};
    evt[PARAM_FLASH_VERSION] = "";
    evt[PARAM_PLAYER_HEIGHT] = "1080"; // Hard-coded to 1080p
    evt[PARAM_PLAYER_WIDTH] = "1920"; // Hard-coded to 1080p
    evt[PARAM_SETUP_TIME] = -1; // TODO: instrument app setup time
    evt[PARAM_VISUAL_PLAYLIST] = 0;
    evt[PARAM_POSTER_IMAGE] = 1;
    evt[PARAM_DISPLAY_DESCRIPTION] = 0;
    evt[PARAM_SKIN_NAME] = "";
    evt[PARAM_CHROMELESS_PLAYER] = 0;
    evt[PARAM_SHARING] = 0;
    evt[PARAM_RELATED] = 0;
    evt[PARAM_CASTING_BLOCK] = 0;
    evt[PARAM_GA_BLOCK] = 0;
    evt[PARAM_DASHBOARD_CONFIG_KEY] = "";
    evt[PARAM_ADVERTISING_BLOCK] = 0; // TODO: Set this appropriately if/when ads are implemented

    _sendEvent(EVENT_VIDEO_EMBED, evt);
  };

  function _numQuantiles(duration) {
    if (duration == 0) return 0;
    else if (duration < 30) return 1;
    else if (duration < 60) return 4;
    else if (duration < 180) return 8;
    else if (duration < 300) return 16;
    else return 32;
  }

  function _pctQuantiles(current, duration) {
    var numq = _numQuantiles(duration);
    var q = 128 / numq;
    var currentQuantile = Math.floor( ( (current / duration) * 128) / q) * q;
    return currentQuantile;
  }

  function _timeDiff(now, last) {
    return Math.floor((now.getTime() - last.getTime())/1000);
  }

  _self._sendTime = function(currentTime) {
    var evt = _mediaParams();

    var currentQuantile = _pctQuantiles(currentTime, item.duration);
    var lastQuantile = _pctQuantiles(lastTime, item.duration)
    var numQuantiles = _numQuantiles(item.duration);

    evt[PARAM_TIME_WATCHED] = currentQuantile;
    evt[PARAM_QUANTILES] = _numQuantiles(item.duration);
    evt[PARAM_TIME_INTERVAL] = _timeDiff(new Date(), lastPingSent);

    if (currentQuantile != lastQuantile) {
      _sendEvent(EVENT_TIME_WATCHED, evt);
    }

    lastTime = currentTime;

  };

}

TVOSAnalytics.prototype.start = function() {
  return this._sendStart();
}

TVOSAnalytics.prototype.embed = function() {
  return this._sendEmbed();
}

TVOSAnalytics.prototype.timeWatched = function(currentTime) {
  return this._sendTime(currentTime);
}
<commit_msg>Analytics tweaks for start event<commit_after>/**
  Analytics for TVOS app.  Ultimately this should mirror the Player's analytics
  plugin, but for MVP, we'll hand-roll something simple, with a lot of
  hard-coded parameters.
**/
function TVOSAnalytics(item) {
  var _self = this,
    serverURL = "jwpltx.com",
    apiVersion = "v1",
    bucketName = "jwplayer6",
    analyticsToken = CONFIG['analyticsToken'],
    embedId = _genId(12),
    lastTime = 0,
    lastPingSent;

  /** @const */ var PARAM_CHECKSUM = 'h';

  //sent with all events
  /** @const */ var PARAM_TRACKER_VERSION     = 'tv';
  /** @const */ var PARAM_NONCE               = 'n';
  /** @const */ var PARAM_ANALYTICS_TOKEN     = 'aid';
  /** @const */ var PARAM_EVENT_TYPE          = 'e';
  /** @const */ var PARAM_IFRAME              = 'i';
  /** @const */ var PARAM_IFRAME_DEPTH        = 'ifd';
  /** @const */ var PARAM_PLAYER_VERSION      = 'pv';
  /** @const */ var PARAM_PAGE_URL            = 'pu';
  /** @const */ var PARAM_PAGE_TITLE          = 'pt';
  /** @const */ var PARAM_SDK_PLATFORM        = 'sdk';
  /** @const */ var PARAM_AUTOSTART           = 'd';
  /** @const */ var PARAM_AD_BLOCK            = 'eb';
  /** @const */ var PARAM_EMBED_ID            = 'emi';
  /** @const */ var PARAM_RENDERING_MODE      = 'm';
  /** @const */ var PARAM_MEDIA_URL           = 'mu';
  /** @const */ var PARAM_PLAYER_HOSTING      = 'ph';
  /** @const */ var PARAM_ITEM_ID             = 'pli';
  /** @const */ var PARAM_PLAYER_SIZE         = 'ps';
  /** @const */ var PARAM_MOBILE_SDK_VERSION  = 'sv';
  /** @const */ var PARAM_TITLE               = 't';

  //mobile SDK specific
  /** @const */ var PARAM_MOBILE_APP_BUNDLEID = 'bi';
  /** @const */ var PARAM_MOBILE_APP_NAME     = 'an';
  /** @const */ var PARAM_MOBILE_DEVICE_ID    = 'did';
  /** @const */ var PARAM_PLAYER_DEVICE_MODEL = 'dm';

  // Tracking Parameter Keys
  /** @const */ var PARAM_EDITION               = 'ed';
  /** @const */ var PARAM_TIME_INTERVAL         = 'ti';
  /** @const */ var PARAM_TIME_WATCHED          = 'pw';
  /** @const */ var PARAM_VIDEO_SIZE            = 'vs';
  /** @const */ var PARAM_PLAYER_WIDTH          = 'wd';
  /** @const */ var PARAM_PLAYER_HEIGHT         = 'pl';
  /** @const */ var PARAM_VIDEO_LENGTH          = 'l';
  /** @const */ var PARAM_QUANTILES             = 'q';
  /** @const */ var PARAM_MEDIA_ID              = 'id';
  /** @const */ var PARAM_FLASH_VERSION         = 'fv';
  /** @const */ var PARAM_SETUP_TIME            = 'st';
  /** @const */ var PARAM_FIRST_FRAME           = 'ff';
  /** @const */ var PARAM_PROVIDER              = 'pp';
  /** @const */ var PARAM_VISUAL_PLAYLIST       = 'vp';
  /** @const */ var PARAM_POSTER_IMAGE          = 'po';
  /** @const */ var PARAM_SHARING               = 's';
  /** @const */ var PARAM_RELATED               = 'r';
  /** @const */ var PARAM_SKIN_NAME             = 'sn';
  /** @const */ var PARAM_CASTING_BLOCK         = 'cb';
  /** @const */ var PARAM_GA_BLOCK              = 'ga';
  /** @const */ var PARAM_PLAY_REASON           = 'pr';
  /** @const */ var PARAM_DASHBOARD_CONFIG_KEY  = 'pid';
  /** @const */ var PARAM_DISPLAY_DESCRIPTION   = 'dd';
  /** @const */ var PARAM_CHROMELESS_PLAYER     = 'cp';
  /** @const */ var PARAM_ADVERTISING_BLOCK     = 'ab';

  // Tracking Events
  /** @const */ var EVENT_VIDEO_EMBED  = 'e';
  /** @const */ var EVENT_VIDEO_PLAY   = 's';
  /** @const */ var EVENT_TIME_WATCHED = 't';

  function _genId(len) {
      return new Array(len+1).join((Math.random().toString(36)+'00000000000000000').slice(2, 18)).slice(0, len);
  }

  function _hashParam(s) {
    s = decodeURIComponent(s);
    var h = 0;

    for (var i = 0; i < s.length; i++) {
      var c = s.charCodeAt(i);
      h = ((h << 5) - h) + c;
      h &= h;
    }

    return `h=${h}`;
  }

  /** Generate ping URL and send it **/
  function _sendEvent(event, data) {
    var parameters = {};
    parameters[PARAM_NONCE] = Math.random().toFixed(16).substr(2, 16);
    parameters[PARAM_ANALYTICS_TOKEN] = analyticsToken;
    parameters[PARAM_EVENT_TYPE] = event;
    parameters[PARAM_SDK_PLATFORM] = 4; // 4 = AppleTV
    parameters[PARAM_MOBILE_SDK_VERSION] = "tvos-0.1";
    parameters[PARAM_TRACKER_VERSION] = "";
    parameters[PARAM_IFRAME] = "";
    parameters[PARAM_IFRAME_DEPTH] = 0;
    parameters[PARAM_PLAYER_VERSION] = "";
    parameters[PARAM_PAGE_URL] = "";
    parameters[PARAM_PAGE_TITLE] = "";
    parameters[PARAM_AUTOSTART] = 1;
    parameters[PARAM_AD_BLOCK] = 0;
    parameters[PARAM_EMBED_ID] = embedId;
    parameters[PARAM_RENDERING_MODE] = "";
    parameters[PARAM_PLAYER_HOSTING] = 0;
    parameters[PARAM_PLAYER_SIZE] = "";
    parameters = extend(parameters, data);

    /** Generate list of key/value URL parameter pairs **/
    var trackingArgs = [];
    for (var key in parameters) {
      var value = encodeURIComponent(parameters[key]);
      trackingArgs.push(`${key}=${value}`);
    }

    /** Generate ping request argument string **/
    var trackingArgsStr = trackingArgs.join("&");

    /** MD5 hash for server-side integrity check **/
    var hash = _hashParam(trackingArgsStr);

    /** Compose ping URL **/
    var trackerURL = `https://${serverURL}/${apiVersion}/${bucketName}/ping.gif?${hash}&${trackingArgsStr}`;

    /** Make a request to the analytics endpoint. No need to wait for a result **/
    var xhr = new XMLHttpRequest();
    xhr.open("GET", trackerURL);
    xhr.send();

    /** Record the last time a ping was sent **/
    lastPingSent = new Date();

    return trackerURL;

  }

  function _mediaParams() {
    var params = {};
    params[PARAM_MEDIA_URL] = item.url;
    params[PARAM_ITEM_ID] = item.externalID;
    params[PARAM_TITLE] = item.title;
    return params;
  }

  _self._sendStart = function() {
    var evt = _mediaParams();
    evt[PARAM_VIDEO_LENGTH] = item.duration;
    evt[PARAM_QUANTILES] = _numQuantiles(item.duration);
    evt[PARAM_VIDEO_SIZE] = 5; // 5 = adaptive
    evt[PARAM_FIRST_FRAME] = -1; // -1 = unsupported
    evt[PARAM_PROVIDER] = "";
    evt[PARAM_PLAY_REASON] = 1; // User interaction  TODO: If we implement auto-play recommendations, this needs to be dynamic

    _sendEvent(EVENT_VIDEO_PLAY, evt);
  };

  _self._sendEmbed = function() {
    var evt = {};
    evt[PARAM_FLASH_VERSION] = "";
    evt[PARAM_PLAYER_HEIGHT] = "1080"; // Hard-coded to 1080p
    evt[PARAM_PLAYER_WIDTH] = "1920"; // Hard-coded to 1080p
    evt[PARAM_SETUP_TIME] = -1; // TODO: instrument app setup time
    evt[PARAM_VISUAL_PLAYLIST] = 0;
    evt[PARAM_POSTER_IMAGE] = 1;
    evt[PARAM_DISPLAY_DESCRIPTION] = 0;
    evt[PARAM_SKIN_NAME] = "";
    evt[PARAM_CHROMELESS_PLAYER] = 0;
    evt[PARAM_SHARING] = 0;
    evt[PARAM_RELATED] = 0;
    evt[PARAM_CASTING_BLOCK] = 0;
    evt[PARAM_GA_BLOCK] = 0;
    evt[PARAM_DASHBOARD_CONFIG_KEY] = "";
    evt[PARAM_ADVERTISING_BLOCK] = 0; // TODO: Set this appropriately if/when ads are implemented

    _sendEvent(EVENT_VIDEO_EMBED, evt);
  };

  function _numQuantiles(duration) {
    if (duration == 0) return 0;
    else if (duration < 30) return 1;
    else if (duration < 60) return 4;
    else if (duration < 180) return 8;
    else if (duration < 300) return 16;
    else return 32;
  }

  function _pctQuantiles(current, duration) {
    var numq = _numQuantiles(duration);
    var q = 128 / numq;
    var currentQuantile = Math.floor( ( (current / duration) * 128) / q) * q;
    return currentQuantile;
  }

  function _timeDiff(now, last) {
    return Math.floor((now.getTime() - last.getTime())/1000);
  }

  _self._sendTime = function(currentTime) {
    var evt = _mediaParams();

    var currentQuantile = _pctQuantiles(currentTime, item.duration);
    var lastQuantile = _pctQuantiles(lastTime, item.duration)
    var numQuantiles = _numQuantiles(item.duration);

    evt[PARAM_TIME_WATCHED] = currentQuantile;
    evt[PARAM_QUANTILES] = _numQuantiles(item.duration);
    evt[PARAM_TIME_INTERVAL] = _timeDiff(new Date(), lastPingSent);

    if (currentQuantile != lastQuantile) {
      _sendEvent(EVENT_TIME_WATCHED, evt);
    }

    lastTime = currentTime;

  };

}

TVOSAnalytics.prototype.start = function() {
  return this._sendStart();
}

TVOSAnalytics.prototype.embed = function() {
  return this._sendEmbed();
}

TVOSAnalytics.prototype.timeWatched = function(currentTime) {
  return this._sendTime(currentTime);
}
<|endoftext|>


<commit_before>var _ = require('underscore');
var parley = require('parley');

var Collection = module.exports = function(definition) {

	// Sync (depending on scheme)
	switch (definition.scheme) {
		case "drop"	: definition.sync = _.bind(definition.adapter.sync.drop, definition.adapter, definition); break;
		case "alter": definition.sync = _.bind(definition.adapter.sync.alter, definition.adapter, definition); break;
		default		: throw new Error('Invalid scheme in '+definition.identity+' model!');
	}

	// Absorb definition methods
	_.extend(this, definition);

	// Define core methods
	this.create = function(values, cb) {
		var collection = this;

		// Get status to get value of auto_increment counter
		if (collection.adapter.status) {
			collection.adapter.status(collection.identity,afterwards);
		}
		else afterwards();

		// Modify values as necessary
		function afterwards(err,status){
			if (err) throw err;

			// Auto increment fields that need it
			collection.adapter.autoIncrement(collection.identity,values,function (err,values) {
				if (err) return cb(err);

				// TODO: Verify constraints using (HULL)

				// Add updatedAt and createdAt
				if (collection.adapter.config.createdAt) values.createdAt = new Date();
				if (collection.adapter.config.updatedAt) values.updatedAt = new Date();

				// Call create method in adapter
				return collection.adapter.create(collection.identity,values,cb);
			});
		}
	};
	// Call find method in adapter
	this.find = function(criteria, cb) {
		return this.adapter.find(this.identity,criteria,cb);
	};
	// Call update method in adapter
	this.update = function(criteria, values, cb) {
		return this.adapter.update(this.identity,criteria,values,cb);
	};
	// Call destroy method in adapter
	this.destroy = function(criteria, cb) {
		return this.adapter.destroy(this.identity,criteria,cb);
	};

	this.lock = function(criteria, cb) {
		this.adapter.lock(this.identity,criteria,cb);
	};
	this.unlock = function(criteria, cb) {
		this.adapter.unlock(this.identity,criteria,cb);
	};

	// Bind instance methods to collection
	_.bindAll(definition);
	_.bindAll(this);
};<commit_msg>Set default scheme to 'alter'.<commit_after>var _ = require('underscore');
var parley = require('parley');

var Collection = module.exports = function(definition) {

	// Sync (depending on scheme)
	definition.scheme = definition.scheme || 'alter';
	switch (definition.scheme) {
		case "drop"	: definition.sync = _.bind(definition.adapter.sync.drop, definition.adapter, definition); break;
		case "alter": definition.sync = _.bind(definition.adapter.sync.alter, definition.adapter, definition); break;

		// Not having a scheme is not an error, just default to alter
		// default		: throw new Error('Invalid scheme in '+definition.identity+' model!');
	}

	// Absorb definition methods
	_.extend(this, definition);

	// Define core methods
	this.create = function(values, cb) {
		var collection = this;

		// Get status to get value of auto_increment counter
		if (collection.adapter.status) {
			collection.adapter.status(collection.identity,afterwards);
		}
		else afterwards();

		// Modify values as necessary
		function afterwards(err,status){
			if (err) throw err;

			// Auto increment fields that need it
			collection.adapter.autoIncrement(collection.identity,values,function (err,values) {
				if (err) return cb(err);

				// TODO: Verify constraints using (HULL)

				// Add updatedAt and createdAt
				if (collection.adapter.config.createdAt) values.createdAt = new Date();
				if (collection.adapter.config.updatedAt) values.updatedAt = new Date();

				// Call create method in adapter
				return collection.adapter.create(collection.identity,values,cb);
			});
		}
	};
	// Call find method in adapter
	this.find = function(criteria, cb) {
		return this.adapter.find(this.identity,criteria,cb);
	};
	// Call update method in adapter
	this.update = function(criteria, values, cb) {
		return this.adapter.update(this.identity,criteria,values,cb);
	};
	// Call destroy method in adapter
	this.destroy = function(criteria, cb) {
		return this.adapter.destroy(this.identity,criteria,cb);
	};

	this.lock = function(criteria, cb) {
		this.adapter.lock(this.identity,criteria,cb);
	};
	this.unlock = function(criteria, cb) {
		this.adapter.unlock(this.identity,criteria,cb);
	};

	// Bind instance methods to collection
	_.bindAll(definition);
	_.bindAll(this);
};<|endoftext|>


<commit_before>const assert = require('assert');
const util = require('./../util/util.js');

const client = require('ganache-cli');
const Coverage = require('./../../lib/coverage');
const Api = require('./../../lib/api')

describe('asserts and requires', () => {
  let coverage;
  let api;

  before(async () => {
    api = new Api({silent: true});
    await api.ganache(client);
  })
  beforeEach(() => coverage = new Coverage());
  after(async() => await api.finish());

  // Assert was covered as a branch up to v0.7.11. But since those
  // conditions are never meant to be fullfilled (and assert is really for smt)
  // people disliked this...
  it('should *not* cover assert statements as branches (pass)', async function() {
    const contract = await util.bootstrapCoverage('assert/Assert', api);
    coverage.addContract(contract.instrumented, util.filePath);
    await contract.instance.a(true);
    const mapping = coverage.generate(contract.data, util.pathPrefix);

    assert.deepEqual(mapping[util.filePath].l, {
      5: 2,
    });
    assert.deepEqual(mapping[util.filePath].b, {});
    assert.deepEqual(mapping[util.filePath].s, {
      1: 2,
    });
    assert.deepEqual(mapping[util.filePath].f, {
      1: 2,
    });
  });

  // NB: truffle/contract replays failing txs as .calls to obtain the revert reason from the return
  // data. Hence the 2X measurements.
  it('should *not* cover assert statements as branches (fail)', async function() {
    const contract = await util.bootstrapCoverage('assert/Assert', api);
    coverage.addContract(contract.instrumented, util.filePath);

    try { await contract.instance.a(false) } catch(err) { /* Invalid opcode */ }

    const mapping = coverage.generate(contract.data, util.pathPrefix);
    assert.deepEqual(mapping[util.filePath].l, {
      5: 4,
    });
    assert.deepEqual(mapping[util.filePath].b, {});
    assert.deepEqual(mapping[util.filePath].s, {
      1: 4,
    });
    assert.deepEqual(mapping[util.filePath].f, {
      1: 4,
    });
  });

  it('should cover multi-line require stmts as `if` statements when they pass', async function() {
    const contract = await util.bootstrapCoverage('assert/RequireMultiline', api);
    coverage.addContract(contract.instrumented, util.filePath);
    await contract.instance.a(true, true, true);
    const mapping = coverage.generate(contract.data, util.pathPrefix);

    assert.deepEqual(mapping[util.filePath].l, {
      5: 1,
    });
    assert.deepEqual(mapping[util.filePath].b, {
      1: [1, 0],
    });
    assert.deepEqual(mapping[util.filePath].s, {
      1: 1,
    });
    assert.deepEqual(mapping[util.filePath].f, {
      1: 1,
    });
  });

  // NB: Truffle replays failing txs as .calls to obtain the revert reason from the return
  // data. Hence the 2X measurements.
  it('should cover multi-line require stmts as `if` statements when they fail', async function() {
    const contract = await util.bootstrapCoverage('assert/RequireMultiline', api);
    coverage.addContract(contract.instrumented, util.filePath);

    try { await contract.instance.a(true, true, false) } catch(err) { /* Revert */ }

    const mapping = coverage.generate(contract.data, util.pathPrefix);

    assert.deepEqual(mapping[util.filePath].l, {
      5: 2,
    });
    assert.deepEqual(mapping[util.filePath].b, {
      1: [0, 2],
    });
    assert.deepEqual(mapping[util.filePath].s, {
      1: 2,
    });
    assert.deepEqual(mapping[util.filePath].f, {
      1: 2,
    });
  });

  it('should cover require statements with method arguments', async function() {
    const contract = await util.bootstrapCoverage('assert/Require-fn', api);
    coverage.addContract(contract.instrumented, util.filePath);
    await contract.instance.a(true);
    const mapping = coverage.generate(contract.data, util.pathPrefix);

    assert.deepEqual(mapping[util.filePath].l, {
      5: 1, 9: 1,
    });
    assert.deepEqual(mapping[util.filePath].b, {
      1: [1, 0],
    });
    assert.deepEqual(mapping[util.filePath].s, {
      1: 1, 2: 1
    });
    assert.deepEqual(mapping[util.filePath].f, {
      1: 1, 2: 1
    });
  });

  it('should cover require statements with method arguments & reason string', async function() {
    const contract = await util.bootstrapCoverage('assert/Require-fn-reason', api);
    coverage.addContract(contract.instrumented, util.filePath);
    await contract.instance.a(true);
    const mapping = coverage.generate(contract.data, util.pathPrefix);

    assert.deepEqual(mapping[util.filePath].l, {
      5: 1, 9: 1,
    });
    assert.deepEqual(mapping[util.filePath].b, {
      1: [1, 0],
    });
    assert.deepEqual(mapping[util.filePath].s, {
      1: 1, 2: 1
    });
    assert.deepEqual(mapping[util.filePath].f, {
      1: 1, 2: 1
    });
  });
});
<commit_msg>Fix assert tests<commit_after>const assert = require('assert');
const util = require('./../util/util.js');

const client = require('ganache-cli');
const Coverage = require('./../../lib/coverage');
const Api = require('./../../lib/api')

describe.only('asserts and requires', () => {
  let coverage;
  let api;

  before(async () => {
    api = new Api({silent: true});
    await api.ganache(client);
  })
  beforeEach(() => coverage = new Coverage());
  after(async() => await api.finish());

  // Assert was covered as a branch up to v0.7.11. But since those
  // conditions are never meant to be fullfilled (and assert is really for smt)
  // people disliked this...
  it('should *not* cover assert statements as branches (pass)', async function() {
    const contract = await util.bootstrapCoverage('assert/Assert', api);
    coverage.addContract(contract.instrumented, util.filePath);
    await contract.instance.a(true);
    const mapping = coverage.generate(contract.data, util.pathPrefix);

    assert.deepEqual(mapping[util.filePath].l, {
      5: 1,
    });
    assert.deepEqual(mapping[util.filePath].b, {});
    assert.deepEqual(mapping[util.filePath].s, {
      1: 1,
    });
    assert.deepEqual(mapping[util.filePath].f, {
      1: 1,
    });
  });

  // NB: truffle/contract replays failing txs as .calls to obtain the revert reason from the return
  // data. Hence the 2X measurements.
  it('should *not* cover assert statements as branches (fail)', async function() {
    const contract = await util.bootstrapCoverage('assert/Assert', api);
    coverage.addContract(contract.instrumented, util.filePath);

    try { await contract.instance.a(false) } catch(err) { /* Invalid opcode */ }

    const mapping = coverage.generate(contract.data, util.pathPrefix);
    assert.deepEqual(mapping[util.filePath].l, {
      5: 2,
    });
    assert.deepEqual(mapping[util.filePath].b, {});
    assert.deepEqual(mapping[util.filePath].s, {
      1: 2,
    });
    assert.deepEqual(mapping[util.filePath].f, {
      1: 2,
    });
  });

  it('should cover multi-line require stmts as `if` statements when they pass', async function() {
    const contract = await util.bootstrapCoverage('assert/RequireMultiline', api);
    coverage.addContract(contract.instrumented, util.filePath);
    await contract.instance.a(true, true, true);
    const mapping = coverage.generate(contract.data, util.pathPrefix);

    assert.deepEqual(mapping[util.filePath].l, {
      5: 1,
    });
    assert.deepEqual(mapping[util.filePath].b, {
      1: [1, 0],
    });
    assert.deepEqual(mapping[util.filePath].s, {
      1: 1,
    });
    assert.deepEqual(mapping[util.filePath].f, {
      1: 1,
    });
  });

  // NB: Truffle replays failing txs as .calls to obtain the revert reason from the return
  // data. Hence the 2X measurements.
  it('should cover multi-line require stmts as `if` statements when they fail', async function() {
    const contract = await util.bootstrapCoverage('assert/RequireMultiline', api);
    coverage.addContract(contract.instrumented, util.filePath);

    try { await contract.instance.a(true, true, false) } catch(err) { /* Revert */ }

    const mapping = coverage.generate(contract.data, util.pathPrefix);

    assert.deepEqual(mapping[util.filePath].l, {
      5: 2,
    });
    assert.deepEqual(mapping[util.filePath].b, {
      1: [0, 2],
    });
    assert.deepEqual(mapping[util.filePath].s, {
      1: 2,
    });
    assert.deepEqual(mapping[util.filePath].f, {
      1: 2,
    });
  });

  it('should cover require statements with method arguments', async function() {
    const contract = await util.bootstrapCoverage('assert/Require-fn', api);
    coverage.addContract(contract.instrumented, util.filePath);
    await contract.instance.a(true);
    const mapping = coverage.generate(contract.data, util.pathPrefix);

    assert.deepEqual(mapping[util.filePath].l, {
      5: 1, 9: 1,
    });
    assert.deepEqual(mapping[util.filePath].b, {
      1: [1, 0],
    });
    assert.deepEqual(mapping[util.filePath].s, {
      1: 1, 2: 1
    });
    assert.deepEqual(mapping[util.filePath].f, {
      1: 1, 2: 1
    });
  });

  it('should cover require statements with method arguments & reason string', async function() {
    const contract = await util.bootstrapCoverage('assert/Require-fn-reason', api);
    coverage.addContract(contract.instrumented, util.filePath);
    await contract.instance.a(true);
    const mapping = coverage.generate(contract.data, util.pathPrefix);

    assert.deepEqual(mapping[util.filePath].l, {
      5: 1, 9: 1,
    });
    assert.deepEqual(mapping[util.filePath].b, {
      1: [1, 0],
    });
    assert.deepEqual(mapping[util.filePath].s, {
      1: 1, 2: 1
    });
    assert.deepEqual(mapping[util.filePath].f, {
      1: 1, 2: 1
    });
  });
});
<|endoftext|>


<commit_before>import {ZONE_MAIN, ZONE_SIDEBOARD} from "../zones";

export default {
  name: "Cockatrice",
  download (name, deck) {
    return `\
<?xml version="1.0" encoding="UTF-8"?>
<cockatrice_deck version="1">
  <deckname>${name}</deckname>
  <zone name="main">
${
  deck[ZONE_MAIN]
    .map(renderDownloadCard)
    .join("\n")
}
  </zone>
  <zone name="side">
${
  deck[ZONE_SIDEBOARD]
    .map(renderDownloadCard)
    .join("\n")
}
  </zone>
</cockatrice_deck>`;
  },
  downloadExtension: ".cod",

  copy(name, deck) {
    return [
      ...deck[ZONE_MAIN].map(renderCopyCard),
      "Sideboard",
      ...deck[ZONE_SIDEBOARD].map(renderCopyCard),
    ].join("\n");
  }
};

function renderDownloadCard ({ count, card }) {
  return `    <card number="${count}" name="${correctName(card)}"/>`;
}

function renderCopyCard ({ count, card }) {
  return `${count} ${correctName(card)}`;
}

function correctName (card) {
  switch (card.layout) {
  case "split":
  case "aftermath":
  case "adventure":
    return card.name;

  case "flip":
  case "transform":
  case "meld":
  case "modal_dfc":
    return card.name.replace(/\s\/\/.*$/, "");

  default:
    return card.name;
  }
}
<commit_msg>add linebreak before sideboard<commit_after>import {ZONE_MAIN, ZONE_SIDEBOARD} from "../zones";

export default {
  name: "Cockatrice",
  download (name, deck) {
    return `\
<?xml version="1.0" encoding="UTF-8"?>
<cockatrice_deck version="1">
  <deckname>${name}</deckname>
  <zone name="main">
${
  deck[ZONE_MAIN]
    .map(renderDownloadCard)
    .join("\n")
}
  </zone>
  <zone name="side">
${
  deck[ZONE_SIDEBOARD]
    .map(renderDownloadCard)
    .join("\n")
}
  </zone>
</cockatrice_deck>`;
  },
  downloadExtension: ".cod",

  copy(name, deck) {
    return [
      ...deck[ZONE_MAIN].map(renderCopyCard),
      "",
      "Sideboard",
      ...deck[ZONE_SIDEBOARD].map(renderCopyCard),
    ].join("\n");
  }
};

function renderDownloadCard ({ count, card }) {
  return `    <card number="${count}" name="${correctName(card)}"/>`;
}

function renderCopyCard ({ count, card }) {
  return `${count} ${correctName(card)}`;
}

function correctName (card) {
  switch (card.layout) {
  case "split":
  case "aftermath":
  case "adventure":
    return card.name;

  case "flip":
  case "transform":
  case "meld":
  case "modal_dfc":
    return card.name.replace(/\s\/\/.*$/, "");

  default:
    return card.name;
  }
}
<|endoftext|>


<commit_before>require('tap-browser-color')()
/*
require('../clear')
require('../triangle')
require('../line')
require('../batch')
require('../depth')
require('../elements')
require('../draw')
require('../blend')
require('../stencil')
require('../polygon-offset')
require('../culling')
require('../misc')
require('../sample.js')
require('../scissor.js')
*/
require('../viewport')
<commit_msg>restore tests<commit_after>require('tap-browser-color')()
require('../clear')
require('../triangle')
require('../line')
require('../batch')
require('../depth')
require('../elements')
require('../draw')
require('../blend')
require('../stencil')
require('../polygon-offset')
require('../culling')
require('../misc')
require('../sample.js')
require('../scissor.js')
require('../viewport')
<|endoftext|>


<commit_before>function init_table(options) {

    options = options || {};
    var csv_path = options.csv_path || "";
    var el = options.element || "table-container";
    var allow_download = options.allow_download || false;
    var csv_options = options.csv_options || {};
    var datatables_options = options.datatables_options || {};

    $("#" + el).html("

");

    $.when($.get(csv_path)).then(
        function(data) {
            var csv_data = $.csv.toArrays(data, csv_options);

            var table_head = "

";

            for (head_id = 0; head_id < csv_data[0].length; head_id++) {
                table_head += "" + csv_data[0][head_id] + "	";
            }

            table_head += "

";
            $('#my-table').append(table_head);
            $('#my-table').append("

");

            for (row_id = 1; row_id < csv_data.length; row_id++) {
                var row_html = "
";
  		csv_data[row_id][6] = linkpdf(csv_data[row_id][6]);
                csv_data[row_id][13] = linkify(csv_data[row_id][13]);
		for (col_id = 0; col_id < csv_data[row_id].length; col_id++) {
                    row_html += "" + csv_data[row_id][col_id] + "	";
                }
                row_html += "
";
                $('#my-table tbody').append(row_html);
            }

            $("#my-table").DataTable(datatables_options);

            if (allow_download)
                $("#" + el).append("
 Download as CSV
");
        });
}

function linkify(inputText) {
    //URLs starting with http://, https://, or ftp://
    var replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
    var replacedText = inputText.replace(replacePattern1, '$1');
    return replacedText
}

function linkpdf(inputText) {
     var linkedText='' + inputText + '';
     return linkedText
}

<commit_msg>rename MR links to shorten table<commit_after>function init_table(options) {

    options = options || {};
    var csv_path = options.csv_path || "";
    var el = options.element || "table-container";
    var allow_download = options.allow_download || false;
    var csv_options = options.csv_options || {};
    var datatables_options = options.datatables_options || {};

    $("#" + el).html("

");

    $.when($.get(csv_path)).then(
        function(data) {
            var csv_data = $.csv.toArrays(data, csv_options);

            var table_head = "

";

            for (head_id = 0; head_id < csv_data[0].length; head_id++) {
                table_head += "" + csv_data[0][head_id] + "	";
            }

            table_head += "

";
            $('#my-table').append(table_head);
            $('#my-table').append("

");

            for (row_id = 1; row_id < csv_data.length; row_id++) {
                var row_html = "
";
  		csv_data[row_id][6] = linkpdf(csv_data[row_id][6]);
                csv_data[row_id][13] = linkify(csv_data[row_id][13]);
		for (col_id = 0; col_id < csv_data[row_id].length; col_id++) {
                    row_html += "" + csv_data[row_id][col_id] + "	";
                }
                row_html += "
";
                $('#my-table tbody').append(row_html);
            }

            $("#my-table").DataTable(datatables_options);

            if (allow_download)
                $("#" + el).append("
 Download as CSV
");
        });
}

function linkify(inputText) {
    //URLs starting with http://, https://, or ftp://
    var replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
    var replacedText = inputText.replace(replacePattern1, 'MuckRock Request');
    return replacedText
}

function linkpdf(inputText) {
     var linkedText='' + inputText + '';
     return linkedText
}

<|endoftext|>


<commit_before>import React from 'react';
import PropTable from './components/propTable';
import { storiesOf } from '@storybook/react';
import { checkA11y } from 'storybook-addon-a11y';
import { withKnobs, boolean, number, select } from '@storybook/addon-knobs/react';
import { withInfo } from '@storybook/addon-info';
import {
  DataGrid,
  Heading4,
  IconMenu,
  MenuItem,
  Link,
  StatusBullet,
  TextSmall,
  Tooltip,
  Button,
  ButtonGroup,
} from '../components';
import { rows1, rows2 } from '../static/data/datagrid';

const TooltippedStatusBullet = Tooltip(StatusBullet);

const handleRowSelectionChange = selectedRows => {
  console.log(`onSelectionChange - selected row indexes: ${selectedRows}`);
};

storiesOf('DataGrids', module)
  .addDecorator((story, context) =>
    withInfo({
      TableComponent: PropTable,
      propTablesExclude: [Link, TextSmall, Heading4, IconMenu, MenuItem],
    })(story)(context),
  )
  .addDecorator(checkA11y)
  .addDecorator(withKnobs)
  .add('Basic', () => (
    <DataGrid
      selectable={boolean('Selectable', true)}
      comparableId={1}
      onSelectionChange={handleRowSelectionChange}
      checkboxSize={select('Checkbox size', ['small', 'medium', 'large'], 'small')}
    >
      <DataGrid.HeaderRowOverlay>
        <Button level="primary" label="Marks as paid" />
        <ButtonGroup segmented marginHorizontal={3}>
          <Button label="Book" />
          <Button label="Merge" />
        </ButtonGroup>
        <Button level="destructive" label="Delete" />
      </DataGrid.HeaderRowOverlay>

      <DataGrid.HeaderRow>
        <DataGrid.HeaderCell flex="min-width" />
        <DataGrid.HeaderCell onClick={() => console.log('onClick: column sort')} sorted="asc">
          Invoice
        </DataGrid.HeaderCell>
        <DataGrid.HeaderCell onClick={() => console.log('onClick: column sort')} align="right">
          Amount
        </DataGrid.HeaderCell>
        <DataGrid.HeaderCell flex="2" onClick={() => console.log('onClick: column sort')}>
          Customer
        </DataGrid.HeaderCell>
        <DataGrid.HeaderCell onClick={() => console.log('onClick: column sort')}>Due date</DataGrid.HeaderCell>
        <DataGrid.HeaderCell flex="min-width" />
      </DataGrid.HeaderRow>
      {rows1.map((row, index) => {
        return (
          <DataGrid.BodyRow key={index}>
            <DataGrid.Cell align="center" flex="min-width">
              <TooltippedStatusBullet
                color={row.column1}
                tooltip={<TextSmall>Overdue</TextSmall>}
                tooltipColor={row.column1}
                tooltipSize="small"
                size="medium"
              />
            </DataGrid.Cell>
            <DataGrid.Cell>
              <Link href="#" inherit={false}>
                {row.column5}
              </Link>{' '}
            </DataGrid.Cell>
            <DataGrid.Cell align="right" strong>
              {' '}
              {`€ ${row.column3}`}
            </DataGrid.Cell>
            <DataGrid.Cell flex="2">{row.column2}</DataGrid.Cell>
            <DataGrid.Cell soft>{row.column4}</DataGrid.Cell>
            <DataGrid.Cell align="right" flex="min-width" preventOverflow={false}>
              <IconMenu position="top-right">
                <MenuItem onClick={() => console.log('onClick: delete row')}>Remove row</MenuItem>
              </IconMenu>
            </DataGrid.Cell>
          </DataGrid.BodyRow>
        );
      })}
    </DataGrid>
  ))
  .add('with footer', () => (
    <DataGrid selectable={boolean('Selectable', true)} comparableId={1} onSelectionChange={handleRowSelectionChange}>
      <DataGrid.HeaderRowOverlay>
        <React.Fragment>
          <Button level="primary" label="Marks as paid" />
          <ButtonGroup segmented marginHorizontal={3}>
            <Button label="Book" />
            <Button label="Merge" />
          </ButtonGroup>
          <Button level="destructive" label="Delete" />
        </React.Fragment>
      </DataGrid.HeaderRowOverlay>

      <DataGrid.HeaderRow>
        <DataGrid.HeaderCell flex="min-width" />
        <DataGrid.HeaderCell onClick={() => console.log('onClick: column sort')} sorted="asc">
          Invoice
        </DataGrid.HeaderCell>
        <DataGrid.HeaderCell onClick={() => console.log('onClick: column sort')} align="right">
          Amount
        </DataGrid.HeaderCell>
        <DataGrid.HeaderCell flex="2" onClick={() => console.log('onClick: column sort')}>
          Customer
        </DataGrid.HeaderCell>
        <DataGrid.HeaderCell onClick={() => console.log('onClick: column sort')}>Due date</DataGrid.HeaderCell>
        <DataGrid.HeaderCell flex="min-width" />
      </DataGrid.HeaderRow>
      {rows2.map((row, index) => {
        return (
          <DataGrid.BodyRow key={index}>
            <DataGrid.Cell align="center" flex="min-width">
              <TooltippedStatusBullet
                color={row.column1}
                tooltip={<TextSmall>Overdue</TextSmall>}
                tooltipColor={row.column1}
                tooltipSize="small"
                size="medium"
              />
            </DataGrid.Cell>
            <DataGrid.Cell>
              <Link href="#" inherit={false}>
                {row.column5}
              </Link>{' '}
            </DataGrid.Cell>
            <DataGrid.Cell align="right" strong>
              {' '}
              {`€ ${row.column3}`}
            </DataGrid.Cell>
            <DataGrid.Cell flex="2">{row.column2}</DataGrid.Cell>
            <DataGrid.Cell soft>{row.column4}</DataGrid.Cell>
            <DataGrid.Cell align="right" flex="min-width" preventOverflow={false}>
              <IconMenu position="top-right">
                <MenuItem onClick={() => console.log('onClick: delete row')}>Remove row</MenuItem>
              </IconMenu>
            </DataGrid.Cell>
          </DataGrid.BodyRow>
        );
      })}
      <DataGrid.FooterRow backgroundColor="neutral">
        <DataGrid.Cell align="right">{`${rows1.length} rows in total`}</DataGrid.Cell>
      </DataGrid.FooterRow>
    </DataGrid>
  ))
  .add('with sticky columns', () => (
    <DataGrid
      selectable={boolean('Selectable', true)}
      stickyFromLeft={number('Sticky from left', 3)}
      stickyFromRight={number('Sticky from right', 1)}
      comparableId={1}
      onSelectionChange={handleRowSelectionChange}
    >
      <DataGrid.HeaderRowOverlay>
        <React.Fragment>
          <Button level="primary" label="Marks as paid" />
          <ButtonGroup segmented marginHorizontal={3}>
            <Button label="Book" />
            <Button label="Merge" />
          </ButtonGroup>
          <Button level="destructive" label="Delete" />
        </React.Fragment>
      </DataGrid.HeaderRowOverlay>

      <DataGrid.HeaderRow>
        <DataGrid.HeaderCell flex="min-width" />
        <DataGrid.HeaderCell onClick={() => console.log('onClick: column sort')} sorted="asc">
          Invoice
        </DataGrid.HeaderCell>
        <DataGrid.HeaderCell onClick={() => console.log('onClick: column sort')} align="right">
          Amount
        </DataGrid.HeaderCell>
        <DataGrid.HeaderCell flex="2" onClick={() => console.log('onClick: column sort')}>
          Customer
        </DataGrid.HeaderCell>
        <DataGrid.HeaderCell onClick={() => console.log('onClick: column sort')}>Due date</DataGrid.HeaderCell>
        <DataGrid.HeaderCell flex="min-width" />
      </DataGrid.HeaderRow>
      {rows1.map((row, index) => {
        return (
          <DataGrid.BodyRow key={index}>
            <DataGrid.Cell align="center" flex="min-width">
              <TooltippedStatusBullet
                color={row.column1}
                tooltip={<TextSmall>Overdue</TextSmall>}
                tooltipColor={row.column1}
                tooltipSize="small"
                size="medium"
              />
            </DataGrid.Cell>
            <DataGrid.Cell>
              <Link href="#" inherit={false}>
                {row.column5}
              </Link>{' '}
            </DataGrid.Cell>
            <DataGrid.Cell align="right" strong>
              {' '}
              {`€ ${row.column3}`}
            </DataGrid.Cell>
            <DataGrid.Cell flex="2">{row.column2}</DataGrid.Cell>
            <DataGrid.Cell soft>{row.column4}</DataGrid.Cell>
            <DataGrid.Cell align="right" flex="min-width" preventOverflow={false}>
              <IconMenu position="top-right">
                <MenuItem onClick={() => console.log('onClick: delete row')}>Remove row</MenuItem>
              </IconMenu>
            </DataGrid.Cell>
          </DataGrid.BodyRow>
        );
      })}
    </DataGrid>
  ))
  .add('Advanced', () => (
    <DataGrid
      selectable={boolean('Selectable', true)}
      stickyFromLeft={number('Sticky from left', 3)}
      stickyFromRight={number('Sticky from right', 1)}
      comparableId={1}
      onSelectionChange={handleRowSelectionChange}
    >
      <DataGrid.HeaderRowOverlay>
        <Button level="primary" label="Marks as paid" />
        <ButtonGroup segmented marginHorizontal={3}>
          <Button label="Book" />
          <Button label="Merge" />
        </ButtonGroup>
        <Button level="destructive" label="Delete" />
      </DataGrid.HeaderRowOverlay>

      <DataGrid.HeaderRow>
        <DataGrid.HeaderCell flex="min-width" />
        <DataGrid.HeaderCell onClick={() => console.log('onClick: column sort')} sorted="asc">
          Invoice
        </DataGrid.HeaderCell>
        <DataGrid.HeaderCell onClick={() => console.log('onClick: column sort')} align="right">
          Amount
        </DataGrid.HeaderCell>
        <DataGrid.HeaderCell flex="2" onClick={() => console.log('onClick: column sort')}>
          Customer
        </DataGrid.HeaderCell>
        <DataGrid.HeaderCell onClick={() => console.log('onClick: column sort')}>Due date</DataGrid.HeaderCell>
        <DataGrid.HeaderCell flex="min-width" />
      </DataGrid.HeaderRow>
      {rows1.map((row, index) => {
        return (
          <DataGrid.BodyRow key={index}>
            <DataGrid.Cell align="center" flex="min-width">
              <TooltippedStatusBullet
                color={row.column1}
                tooltip={<TextSmall>Overdue</TextSmall>}
                tooltipColor={row.column1}
                tooltipSize="small"
                size="medium"
              />
            </DataGrid.Cell>
            <DataGrid.Cell>
              <Link href="#" inherit={false}>
                {row.column5}
              </Link>{' '}
            </DataGrid.Cell>
            <DataGrid.Cell align="right" strong>
              {' '}
              {`€ ${row.column3}`}
            </DataGrid.Cell>
            <DataGrid.Cell flex="2">{row.column2}</DataGrid.Cell>
            <DataGrid.Cell soft>{row.column4}</DataGrid.Cell>
            <DataGrid.Cell align="right" flex="min-width" preventOverflow={false}>
              <IconMenu position="top-right">
                <MenuItem onClick={() => console.log('onClick: delete row')}>Remove row</MenuItem>
              </IconMenu>
            </DataGrid.Cell>
          </DataGrid.BodyRow>
        );
      })}
      <DataGrid.FooterRow backgroundColor="neutral">
        <DataGrid.Cell flex="min-width" />
        <DataGrid.Cell align="right" soft>
          <Heading4>Total Excl Btw</Heading4>
        </DataGrid.Cell>
        <DataGrid.Cell align="right" strong>
          € 13460.52
        </DataGrid.Cell>
        <DataGrid.Cell flex="2" />
        <DataGrid.Cell />
        <DataGrid.Cell flex="min-width" />
      </DataGrid.FooterRow>

      <DataGrid.FooterRow backgroundColor="neutral">
        <DataGrid.Cell flex="min-width" />
        <DataGrid.Cell align="right" soft>
          <Heading4>+ VAT</Heading4>
        </DataGrid.Cell>
        <DataGrid.Cell align="right" strong>
          € 2826.71
        </DataGrid.Cell>
        <DataGrid.Cell flex="2" />
        <DataGrid.Cell />
        <DataGrid.Cell flex="min-width" />
      </DataGrid.FooterRow>

      <DataGrid.FooterRow backgroundColor="neutral">
        <DataGrid.Cell flex="min-width" />
        <DataGrid.Cell align="right" soft>
          <Heading4>Total Incl Btw</Heading4>
        </DataGrid.Cell>
        <DataGrid.Cell align="right" strong backgroundColor="white" border="around">
          € 16287.23
        </DataGrid.Cell>
        <DataGrid.Cell flex="2" />
        <DataGrid.Cell />
        <DataGrid.Cell flex="min-width" />
      </DataGrid.FooterRow>
    </DataGrid>
  ));
<commit_msg>Use smaller buttons for more white space<commit_after>import React from 'react';
import PropTable from './components/propTable';
import { storiesOf } from '@storybook/react';
import { checkA11y } from 'storybook-addon-a11y';
import { withKnobs, boolean, number, select } from '@storybook/addon-knobs/react';
import { withInfo } from '@storybook/addon-info';
import {
  DataGrid,
  Heading4,
  IconMenu,
  MenuItem,
  Link,
  StatusBullet,
  TextSmall,
  Tooltip,
  Button,
  ButtonGroup,
} from '../components';
import { rows1, rows2 } from '../static/data/datagrid';

const TooltippedStatusBullet = Tooltip(StatusBullet);

const handleRowSelectionChange = selectedRows => {
  console.log(`onSelectionChange - selected row indexes: ${selectedRows}`);
};

storiesOf('DataGrids', module)
  .addDecorator((story, context) =>
    withInfo({
      TableComponent: PropTable,
      propTablesExclude: [Link, TextSmall, Heading4, IconMenu, MenuItem],
    })(story)(context),
  )
  .addDecorator(checkA11y)
  .addDecorator(withKnobs)
  .add('Basic', () => (
    <DataGrid
      selectable={boolean('Selectable', true)}
      comparableId={1}
      onSelectionChange={handleRowSelectionChange}
      checkboxSize={select('Checkbox size', ['small', 'medium', 'large'], 'small')}
    >
      <DataGrid.HeaderRowOverlay>
        <Button size="small" level="primary" label="Marks as paid" />
        <ButtonGroup segmented marginHorizontal={3}>
          <Button size="small" label="Book" />
          <Button size="small" label="Merge" />
        </ButtonGroup>
        <Button size="small" level="destructive" label="Delete" />
      </DataGrid.HeaderRowOverlay>

      <DataGrid.HeaderRow>
        <DataGrid.HeaderCell flex="min-width" />
        <DataGrid.HeaderCell onClick={() => console.log('onClick: column sort')} sorted="asc">
          Invoice
        </DataGrid.HeaderCell>
        <DataGrid.HeaderCell onClick={() => console.log('onClick: column sort')} align="right">
          Amount
        </DataGrid.HeaderCell>
        <DataGrid.HeaderCell flex="2" onClick={() => console.log('onClick: column sort')}>
          Customer
        </DataGrid.HeaderCell>
        <DataGrid.HeaderCell onClick={() => console.log('onClick: column sort')}>Due date</DataGrid.HeaderCell>
        <DataGrid.HeaderCell flex="min-width" />
      </DataGrid.HeaderRow>
      {rows1.map((row, index) => {
        return (
          <DataGrid.BodyRow key={index}>
            <DataGrid.Cell align="center" flex="min-width">
              <TooltippedStatusBullet
                color={row.column1}
                tooltip={<TextSmall>Overdue</TextSmall>}
                tooltipColor={row.column1}
                tooltipSize="small"
                size="medium"
              />
            </DataGrid.Cell>
            <DataGrid.Cell>
              <Link href="#" inherit={false}>
                {row.column5}
              </Link>{' '}
            </DataGrid.Cell>
            <DataGrid.Cell align="right" strong>
              {' '}
              {`€ ${row.column3}`}
            </DataGrid.Cell>
            <DataGrid.Cell flex="2">{row.column2}</DataGrid.Cell>
            <DataGrid.Cell soft>{row.column4}</DataGrid.Cell>
            <DataGrid.Cell align="right" flex="min-width" preventOverflow={false}>
              <IconMenu position="top-right">
                <MenuItem onClick={() => console.log('onClick: delete row')}>Remove row</MenuItem>
              </IconMenu>
            </DataGrid.Cell>
          </DataGrid.BodyRow>
        );
      })}
    </DataGrid>
  ))
  .add('with footer', () => (
    <DataGrid selectable={boolean('Selectable', true)} comparableId={1} onSelectionChange={handleRowSelectionChange}>
      <DataGrid.HeaderRowOverlay>
        <React.Fragment>
          <Button size="small" level="primary" label="Marks as paid" />
          <ButtonGroup segmented marginHorizontal={3}>
            <Button size="small" label="Book" />
            <Button size="small" label="Merge" />
          </ButtonGroup>
          <Button size="small" level="destructive" label="Delete" />
        </React.Fragment>
      </DataGrid.HeaderRowOverlay>

      <DataGrid.HeaderRow>
        <DataGrid.HeaderCell flex="min-width" />
        <DataGrid.HeaderCell onClick={() => console.log('onClick: column sort')} sorted="asc">
          Invoice
        </DataGrid.HeaderCell>
        <DataGrid.HeaderCell onClick={() => console.log('onClick: column sort')} align="right">
          Amount
        </DataGrid.HeaderCell>
        <DataGrid.HeaderCell flex="2" onClick={() => console.log('onClick: column sort')}>
          Customer
        </DataGrid.HeaderCell>
        <DataGrid.HeaderCell onClick={() => console.log('onClick: column sort')}>Due date</DataGrid.HeaderCell>
        <DataGrid.HeaderCell flex="min-width" />
      </DataGrid.HeaderRow>
      {rows2.map((row, index) => {
        return (
          <DataGrid.BodyRow key={index}>
            <DataGrid.Cell align="center" flex="min-width">
              <TooltippedStatusBullet
                color={row.column1}
                tooltip={<TextSmall>Overdue</TextSmall>}
                tooltipColor={row.column1}
                tooltipSize="small"
                size="medium"
              />
            </DataGrid.Cell>
            <DataGrid.Cell>
              <Link href="#" inherit={false}>
                {row.column5}
              </Link>{' '}
            </DataGrid.Cell>
            <DataGrid.Cell align="right" strong>
              {' '}
              {`€ ${row.column3}`}
            </DataGrid.Cell>
            <DataGrid.Cell flex="2">{row.column2}</DataGrid.Cell>
            <DataGrid.Cell soft>{row.column4}</DataGrid.Cell>
            <DataGrid.Cell align="right" flex="min-width" preventOverflow={false}>
              <IconMenu position="top-right">
                <MenuItem onClick={() => console.log('onClick: delete row')}>Remove row</MenuItem>
              </IconMenu>
            </DataGrid.Cell>
          </DataGrid.BodyRow>
        );
      })}
      <DataGrid.FooterRow backgroundColor="neutral">
        <DataGrid.Cell align="right">{`${rows1.length} rows in total`}</DataGrid.Cell>
      </DataGrid.FooterRow>
    </DataGrid>
  ))
  .add('with sticky columns', () => (
    <DataGrid
      selectable={boolean('Selectable', true)}
      stickyFromLeft={number('Sticky from left', 3)}
      stickyFromRight={number('Sticky from right', 1)}
      comparableId={1}
      onSelectionChange={handleRowSelectionChange}
    >
      <DataGrid.HeaderRowOverlay>
        <React.Fragment>
          <Button size="small" level="primary" label="Marks as paid" />
          <ButtonGroup segmented marginHorizontal={3}>
            <Button size="small" label="Book" />
            <Button size="small" label="Merge" />
          </ButtonGroup>
          <Button size="small" level="destructive" label="Delete" />
        </React.Fragment>
      </DataGrid.HeaderRowOverlay>

      <DataGrid.HeaderRow>
        <DataGrid.HeaderCell flex="min-width" />
        <DataGrid.HeaderCell onClick={() => console.log('onClick: column sort')} sorted="asc">
          Invoice
        </DataGrid.HeaderCell>
        <DataGrid.HeaderCell onClick={() => console.log('onClick: column sort')} align="right">
          Amount
        </DataGrid.HeaderCell>
        <DataGrid.HeaderCell flex="2" onClick={() => console.log('onClick: column sort')}>
          Customer
        </DataGrid.HeaderCell>
        <DataGrid.HeaderCell onClick={() => console.log('onClick: column sort')}>Due date</DataGrid.HeaderCell>
        <DataGrid.HeaderCell flex="min-width" />
      </DataGrid.HeaderRow>
      {rows1.map((row, index) => {
        return (
          <DataGrid.BodyRow key={index}>
            <DataGrid.Cell align="center" flex="min-width">
              <TooltippedStatusBullet
                color={row.column1}
                tooltip={<TextSmall>Overdue</TextSmall>}
                tooltipColor={row.column1}
                tooltipSize="small"
                size="medium"
              />
            </DataGrid.Cell>
            <DataGrid.Cell>
              <Link href="#" inherit={false}>
                {row.column5}
              </Link>{' '}
            </DataGrid.Cell>
            <DataGrid.Cell align="right" strong>
              {' '}
              {`€ ${row.column3}`}
            </DataGrid.Cell>
            <DataGrid.Cell flex="2">{row.column2}</DataGrid.Cell>
            <DataGrid.Cell soft>{row.column4}</DataGrid.Cell>
            <DataGrid.Cell align="right" flex="min-width" preventOverflow={false}>
              <IconMenu position="top-right">
                <MenuItem onClick={() => console.log('onClick: delete row')}>Remove row</MenuItem>
              </IconMenu>
            </DataGrid.Cell>
          </DataGrid.BodyRow>
        );
      })}
    </DataGrid>
  ))
  .add('Advanced', () => (
    <DataGrid
      selectable={boolean('Selectable', true)}
      stickyFromLeft={number('Sticky from left', 3)}
      stickyFromRight={number('Sticky from right', 1)}
      comparableId={1}
      onSelectionChange={handleRowSelectionChange}
    >
      <DataGrid.HeaderRowOverlay>
        <Button size="small" level="primary" label="Marks as paid" />
        <ButtonGroup segmented marginHorizontal={3}>
          <Button size="small" label="Book" />
          <Button size="small" label="Merge" />
        </ButtonGroup>
        <Button size="small" level="destructive" label="Delete" />
      </DataGrid.HeaderRowOverlay>

      <DataGrid.HeaderRow>
        <DataGrid.HeaderCell flex="min-width" />
        <DataGrid.HeaderCell onClick={() => console.log('onClick: column sort')} sorted="asc">
          Invoice
        </DataGrid.HeaderCell>
        <DataGrid.HeaderCell onClick={() => console.log('onClick: column sort')} align="right">
          Amount
        </DataGrid.HeaderCell>
        <DataGrid.HeaderCell flex="2" onClick={() => console.log('onClick: column sort')}>
          Customer
        </DataGrid.HeaderCell>
        <DataGrid.HeaderCell onClick={() => console.log('onClick: column sort')}>Due date</DataGrid.HeaderCell>
        <DataGrid.HeaderCell flex="min-width" />
      </DataGrid.HeaderRow>
      {rows1.map((row, index) => {
        return (
          <DataGrid.BodyRow key={index}>
            <DataGrid.Cell align="center" flex="min-width">
              <TooltippedStatusBullet
                color={row.column1}
                tooltip={<TextSmall>Overdue</TextSmall>}
                tooltipColor={row.column1}
                tooltipSize="small"
                size="medium"
              />
            </DataGrid.Cell>
            <DataGrid.Cell>
              <Link href="#" inherit={false}>
                {row.column5}
              </Link>{' '}
            </DataGrid.Cell>
            <DataGrid.Cell align="right" strong>
              {' '}
              {`€ ${row.column3}`}
            </DataGrid.Cell>
            <DataGrid.Cell flex="2">{row.column2}</DataGrid.Cell>
            <DataGrid.Cell soft>{row.column4}</DataGrid.Cell>
            <DataGrid.Cell align="right" flex="min-width" preventOverflow={false}>
              <IconMenu position="top-right">
                <MenuItem onClick={() => console.log('onClick: delete row')}>Remove row</MenuItem>
              </IconMenu>
            </DataGrid.Cell>
          </DataGrid.BodyRow>
        );
      })}
      <DataGrid.FooterRow backgroundColor="neutral">
        <DataGrid.Cell flex="min-width" />
        <DataGrid.Cell align="right" soft>
          <Heading4>Total Excl Btw</Heading4>
        </DataGrid.Cell>
        <DataGrid.Cell align="right" strong>
          € 13460.52
        </DataGrid.Cell>
        <DataGrid.Cell flex="2" />
        <DataGrid.Cell />
        <DataGrid.Cell flex="min-width" />
      </DataGrid.FooterRow>

      <DataGrid.FooterRow backgroundColor="neutral">
        <DataGrid.Cell flex="min-width" />
        <DataGrid.Cell align="right" soft>
          <Heading4>+ VAT</Heading4>
        </DataGrid.Cell>
        <DataGrid.Cell align="right" strong>
          € 2826.71
        </DataGrid.Cell>
        <DataGrid.Cell flex="2" />
        <DataGrid.Cell />
        <DataGrid.Cell flex="min-width" />
      </DataGrid.FooterRow>

      <DataGrid.FooterRow backgroundColor="neutral">
        <DataGrid.Cell flex="min-width" />
        <DataGrid.Cell align="right" soft>
          <Heading4>Total Incl Btw</Heading4>
        </DataGrid.Cell>
        <DataGrid.Cell align="right" strong backgroundColor="white" border="around">
          € 16287.23
        </DataGrid.Cell>
        <DataGrid.Cell flex="2" />
        <DataGrid.Cell />
        <DataGrid.Cell flex="min-width" />
      </DataGrid.FooterRow>
    </DataGrid>
  ));
<|endoftext|>


<commit_before>function substr_count (haystack, needle, offset, length) {
    // http://kevin.vanzonneveld.net
    // +   original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // +   bugfixed by: Onno Marsman
    // *     example 1: substr_count('Kevin van Zonneveld', 'e');
    // *     returns 1: 3
    // *     example 2: substr_count('Kevin van Zonneveld', 'K', 1);
    // *     returns 2: 0
    // *     example 3: substr_count('Kevin van Zonneveld', 'Z', 0, 10);
    // *     returns 3: false

    var pos = 0, cnt = 0;

    haystack += '';
    needle += '';
    if (isNaN(offset)) {offset = 0;}
    if (isNaN(length)) {length = 0;}
    offset--;

    while ((offset = haystack.indexOf(needle, offset+1)) != -1){
        if (length > 0 && (offset+needle.length) > length){
            return false;
        } else{
            cnt++;
        }
    }

    return cnt;
}<commit_msg>Simplify function slightly<commit_after>function substr_count (haystack, needle, offset, length) {
    // http://kevin.vanzonneveld.net
    // +   original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // +   bugfixed by: Onno Marsman
    // *     example 1: substr_count('Kevin van Zonneveld', 'e');
    // *     returns 1: 3
    // *     example 2: substr_count('Kevin van Zonneveld', 'K', 1);
    // *     returns 2: 0
    // *     example 3: substr_count('Kevin van Zonneveld', 'Z', 0, 10);
    // *     returns 3: false

    var pos = 0, cnt = 0;

    haystack += '';
    needle += '';
    if (isNaN(offset)) {offset = 0;}
    if (isNaN(length)) {length = 0;}
    offset--;

    while ((offset = haystack.indexOf(needle, offset+1)) != -1){
        if (length > 0 && (offset+needle.length) > length){
            return false;
        }
        cnt++;
    }

    return cnt;
}<|endoftext|>


<commit_before>'use strict';

const http = require('http');

function curl (options, method, path, body) {
    return new Promise((resolve, reject) => {
        const requestOptions = {
                method: method,
                path: path,
                port: options.port,
                hostname: options.host || 'localhost',
                headers: {
                    'Content-Type': 'application/json',
                    Connection: 'close'
                }
            };

        if (options.apikey) {
            requestOptions.headers['x-api-key'] = options.apikey;
        }

        const request = http.request(requestOptions, response => {
            response.body = '';
            response.setEncoding('utf8');
            response.on('data', chunk => { response.body += chunk; });
            response.on('end', () => {
                if (response.statusCode === 200) {
                    response.body = JSON.parse(response.body);
                    resolve(response);
                }
                else {
                    reject(new Error(`${response.statusCode}\n${response.body}`));
                }
            });
        });

        request.on('error', reject);

        if (body) {
            request.write(JSON.stringify(body, null, 2));
        }
        request.end();
    });
}

function putImposters (options, body) {
    return curl(options, 'PUT', '/imposters', body);
}

function getImposters (options) {
    let path = '/imposters?replayable=true';
    if (options.removeProxies) {
        path += '&removeProxies=true';
    }
    return curl(options, 'GET', path);
}

function logConnectionErrorAndExit (options, err) {
    const host = options.host || 'localhost';
    if (err.code === 'ENOENT') {
        console.error(`No such file: ${options.configfile}`);
    }
    if (err.code === 'ECONNREFUSED') {
        console.error(`No mb process running on http://${host}:${options.port}`);
    }
    else {
        console.error(err);
    }
    process.exit(1); // eslint-disable-line no-process-exit
}

async function loadConfig (options) {
    const formatter = require(options.formatter);

    try {
        const imposters = await formatter.load(options);
        await putImposters(options, imposters);
    }
    catch (e) {
        logConnectionErrorAndExit(options, e);
    }
}

async function save (options) {
    const formatter = require(options.formatter);

    try {
        const response = await getImposters(options);
        await formatter.save(options, response.body);
    }
    catch (e) {
        logConnectionErrorAndExit(options, e);
    }
}

async function replay (options) {
    options.removeProxies = true;

    try {
        const response = await getImposters(options);
        await putImposters(options, response.body);
    }
    catch (e) {
        logConnectionErrorAndExit(e, options);
    }
}

module.exports = {
    loadConfig,
    save,
    replay
};
<commit_msg>fixed linting error in api.js<commit_after>'use strict';

const http = require('http');

function curl (options, method, path, body) {
    return new Promise((resolve, reject) => {
        const requestOptions = {
            method: method,
            path: path,
            port: options.port,
            hostname: options.host || 'localhost',
            headers: {
                'Content-Type': 'application/json',
                Connection: 'close'
            }
        };

        if (options.apikey) {
            requestOptions.headers['x-api-key'] = options.apikey;
        }

        const request = http.request(requestOptions, response => {
            response.body = '';
            response.setEncoding('utf8');
            response.on('data', chunk => { response.body += chunk; });
            response.on('end', () => {
                if (response.statusCode === 200) {
                    response.body = JSON.parse(response.body);
                    resolve(response);
                }
                else {
                    reject(new Error(`${response.statusCode}\n${response.body}`));
                }
            });
        });

        request.on('error', reject);

        if (body) {
            request.write(JSON.stringify(body, null, 2));
        }
        request.end();
    });
}

function putImposters (options, body) {
    return curl(options, 'PUT', '/imposters', body);
}

function getImposters (options) {
    let path = '/imposters?replayable=true';
    if (options.removeProxies) {
        path += '&removeProxies=true';
    }
    return curl(options, 'GET', path);
}

function logConnectionErrorAndExit (options, err) {
    const host = options.host || 'localhost';
    if (err.code === 'ENOENT') {
        console.error(`No such file: ${options.configfile}`);
    }
    if (err.code === 'ECONNREFUSED') {
        console.error(`No mb process running on http://${host}:${options.port}`);
    }
    else {
        console.error(err);
    }
    process.exit(1); // eslint-disable-line no-process-exit
}

async function loadConfig (options) {
    const formatter = require(options.formatter);

    try {
        const imposters = await formatter.load(options);
        await putImposters(options, imposters);
    }
    catch (e) {
        logConnectionErrorAndExit(options, e);
    }
}

async function save (options) {
    const formatter = require(options.formatter);

    try {
        const response = await getImposters(options);
        await formatter.save(options, response.body);
    }
    catch (e) {
        logConnectionErrorAndExit(options, e);
    }
}

async function replay (options) {
    options.removeProxies = true;

    try {
        const response = await getImposters(options);
        await putImposters(options, response.body);
    }
    catch (e) {
        logConnectionErrorAndExit(e, options);
    }
}

module.exports = {
    loadConfig,
    save,
    replay
};
<|endoftext|>