<commit_before>/*
This file is part of the Juju GUI, which lets users view and manage Juju
environments within a graphical interface (https://launchpad.net/juju-gui).
Copyright (C) 2012-2013 Canonical Ltd.

This program is free software: you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License version 3, as published by
the Free Software Foundation.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero
General Public License for more details.

You should have received a copy of the GNU Affero General Public License along
with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

'use strict';
// Test utils.

YUI(GlobalConfig).add('juju-tests-utils', function(Y) {
  var jujuTests = Y.namespace('juju-tests');

  jujuTests.utils = {

    makeContainer: function(id, visibleContainer) {
      var container = Y.Node.create('<div>');
      if (id) {
        container.set('id', id);
      }
      container.appendTo(document.body);
      if (visibleContainer !== false) {
        container.setStyle('position', 'absolute');
        container.setStyle('top', '-10000px');
        container.setStyle('left', '-10000px');
      }
      return container;
    },

    SocketStub: function() {
      this.messages = [];

      this.close = function() {
        this.messages = [];
      };

      this.transient_close = function() {
        this.onclose();
      };

      this.open = function() {
        this.onopen();
        return this;
      };

      this.msg = function(m) {
        this.onmessage({'data': Y.JSON.stringify(m)});
      };

      this.last_message = function(back) {
        if (!back) {
          back = 1;
        }
        return this.messages[this.messages.length - back];
      };

      this.send = function(m) {
        this.messages.push(Y.JSON.parse(m));
      };

      this.onclose = function() {};
      this.onmessage = function() {};
      this.onopen = function() {};

    },

    getter: function(attributes, default_) {
      return function(name) {
        if (attributes.hasOwnProperty(name)) {
          return attributes[name];
        } else {
          return default_;
        }
      };
    },

    setter: function(attributes) {
      return function(name, value) {
        attributes[name] = value;
      };
    },

    /**
     * Util to load a fixture (typically as 'data/filename.json').
     *
     * @method loadFixture
     * @param {String} url to synchronously load.
     * @param {Boolean} parseJSON when true return will be processed
     *                  as a JSON blob before returning.
     * @return {Object} fixture data resulting from call.
     */
    loadFixture: function(url, parseJson) {
      var tries = 3;
      var response;
      while (tries) {
        try {
          response= Y.io(url, {sync: true}).responseText;
          if (parseJson) {
            response = Y.JSON.parse(response);
          }
        } catch (e) {
          debugger;
          tries -= 1;
        }
      }
      return response;
    },

    stubCharmIconPath: function() {
      var helperNS = Y.namespace('Handlebars.helpers');
      Y.Handlebars.registerHelper(
          'charmIconPath',
          function(charmID, file) {
            return '/path/to/charm/' + file;
          });

      // Return a cleanup function to undo this change.
      return function() {
        helperNS.charmIconPath = undefined;
      };
    }
  };

  // Split jujuTests.utils definition, so that charms can be cached
  // right away, while at the same time reusing the loadFixture method.
  Y.mix(jujuTests.utils, {

    _cached_charms: (function() {
      var url, charms = {},
          names = [
            'wordpress', 'mysql', 'puppet', 'haproxy', 'mediawiki', 'hadoop',
            'memcached', 'puppetmaster'];
      Y.Array.each(names, function(name) {
        url = 'data/' + name + '-api-response.json';
        charms[name] = jujuTests.utils.loadFixture(url, true);
      });
      return charms;
    })(),

    makeFakeStore: function() {
      var fakeStore = new Y.juju.charmworld.APIv3({});
      fakeStore.charm = function(store_id, callbacks, bindscope, cache) {
        store_id = this.apiHelper.normalizeCharmId(store_id, 'precise');
        var charmName = store_id.split('/')[1];
        charmName = charmName.split('-', 1);
        if (Y.Lang.isArray(charmName)) {
          charmName = charmName[0];
        }
        if (charmName in jujuTests.utils._cached_charms) {
          var response = jujuTests.utils._cached_charms[charmName];
          if (cache) {
            cache.add(response.charm);
          }
          callbacks.success(response);
        } else {
          callbacks.failure(new Error('Unable to load charm ' + charmName));
        }
      };
      return fakeStore;
    },

    makeFakeBackend: function() {
      var fakeStore = this.makeFakeStore();
      var fakebackend = new Y.juju.environments.FakeBackend({
        store: fakeStore
      });
      fakebackend.login('admin', 'password');
      return fakebackend;
    },

    /**
     Return a promise to return a working fakebackend
     with imported YAML as its bundle. This returns
     the result of the import call as 'result' and
     the new fakebackend as 'backend'.

     promiseImport('data/bundle.yaml', 'bundleName')
     .then(function(resolve) {
      var fakebackend = resolve.backend;
      var result = resolve.result;
      // Asserts.
      done();
     })

      @method promiseImport
      @param {String} YAMLBundleURL File to import.
      @param {String} [name] Name of bundle to load, optional when
             only one target in the bundle.
      @param {Object} fakebackend An instance of fakebackend from the
              utils makeFakeBackend() method.
      @return {Promise} Outlined in description.
    */
    promiseImport: function(YAMLBundleURL, name, fakebackend) {
      if (!fakebackend) {
        fakebackend = this.makeFakeBackend();
      }
      var db = fakebackend.db;
      db.environment.set('defaultSeries', 'precise');
      var fixture = jujuTests.utils.loadFixture(YAMLBundleURL);
      return fakebackend.promiseImport(fixture, name)
             .then(function(result) {
               return {result: result, backend: fakebackend};
             });
    }
  });
}, '0.1.0', {
  requires: [
    'handlebars',
    'io',
    'node',
    'promise',
    'json-parse',
    'datasource-local',
    'juju-charm-store',
    'juju-env-fakebackend'
  ]
});
<commit_msg>Allow debugger stmt for testing.<commit_after>/*
This file is part of the Juju GUI, which lets users view and manage Juju
environments within a graphical interface (https://launchpad.net/juju-gui).
Copyright (C) 2012-2013 Canonical Ltd.

This program is free software: you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License version 3, as published by
the Free Software Foundation.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero
General Public License for more details.

You should have received a copy of the GNU Affero General Public License along
with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

'use strict';
// Test utils.

YUI(GlobalConfig).add('juju-tests-utils', function(Y) {
  var jujuTests = Y.namespace('juju-tests');

  jujuTests.utils = {

    makeContainer: function(id, visibleContainer) {
      var container = Y.Node.create('<div>');
      if (id) {
        container.set('id', id);
      }
      container.appendTo(document.body);
      if (visibleContainer !== false) {
        container.setStyle('position', 'absolute');
        container.setStyle('top', '-10000px');
        container.setStyle('left', '-10000px');
      }
      return container;
    },

    SocketStub: function() {
      this.messages = [];

      this.close = function() {
        this.messages = [];
      };

      this.transient_close = function() {
        this.onclose();
      };

      this.open = function() {
        this.onopen();
        return this;
      };

      this.msg = function(m) {
        this.onmessage({'data': Y.JSON.stringify(m)});
      };

      this.last_message = function(back) {
        if (!back) {
          back = 1;
        }
        return this.messages[this.messages.length - back];
      };

      this.send = function(m) {
        this.messages.push(Y.JSON.parse(m));
      };

      this.onclose = function() {};
      this.onmessage = function() {};
      this.onopen = function() {};

    },

    getter: function(attributes, default_) {
      return function(name) {
        if (attributes.hasOwnProperty(name)) {
          return attributes[name];
        } else {
          return default_;
        }
      };
    },

    setter: function(attributes) {
      return function(name, value) {
        attributes[name] = value;
      };
    },

    /**
     * Util to load a fixture (typically as 'data/filename.json').
     *
     * @method loadFixture
     * @param {String} url to synchronously load.
     * @param {Boolean} parseJSON when true return will be processed
     *                  as a JSON blob before returning.
     * @return {Object} fixture data resulting from call.
     */
    loadFixture: function(url, parseJson) {
      var tries = 3;
      var response;
      while (tries) {
        try {
          response= Y.io(url, {sync: true}).responseText;
          if (parseJson) {
            response = Y.JSON.parse(response);
          }
        } catch (e) {
          /*jshint debug:true */
          debugger;
          /*jshint debug:false */
          tries -= 1;
        }
      }
      return response;
    },

    stubCharmIconPath: function() {
      var helperNS = Y.namespace('Handlebars.helpers');
      Y.Handlebars.registerHelper(
          'charmIconPath',
          function(charmID, file) {
            return '/path/to/charm/' + file;
          });

      // Return a cleanup function to undo this change.
      return function() {
        helperNS.charmIconPath = undefined;
      };
    }
  };

  // Split jujuTests.utils definition, so that charms can be cached
  // right away, while at the same time reusing the loadFixture method.
  Y.mix(jujuTests.utils, {

    _cached_charms: (function() {
      var url, charms = {},
          names = [
            'wordpress', 'mysql', 'puppet', 'haproxy', 'mediawiki', 'hadoop',
            'memcached', 'puppetmaster'];
      Y.Array.each(names, function(name) {
        url = 'data/' + name + '-api-response.json';
        charms[name] = jujuTests.utils.loadFixture(url, true);
      });
      return charms;
    })(),

    makeFakeStore: function() {
      var fakeStore = new Y.juju.charmworld.APIv3({});
      fakeStore.charm = function(store_id, callbacks, bindscope, cache) {
        store_id = this.apiHelper.normalizeCharmId(store_id, 'precise');
        var charmName = store_id.split('/')[1];
        charmName = charmName.split('-', 1);
        if (Y.Lang.isArray(charmName)) {
          charmName = charmName[0];
        }
        if (charmName in jujuTests.utils._cached_charms) {
          var response = jujuTests.utils._cached_charms[charmName];
          if (cache) {
            cache.add(response.charm);
          }
          callbacks.success(response);
        } else {
          callbacks.failure(new Error('Unable to load charm ' + charmName));
        }
      };
      return fakeStore;
    },

    makeFakeBackend: function() {
      var fakeStore = this.makeFakeStore();
      var fakebackend = new Y.juju.environments.FakeBackend({
        store: fakeStore
      });
      fakebackend.login('admin', 'password');
      return fakebackend;
    },

    /**
     Return a promise to return a working fakebackend
     with imported YAML as its bundle. This returns
     the result of the import call as 'result' and
     the new fakebackend as 'backend'.

     promiseImport('data/bundle.yaml', 'bundleName')
     .then(function(resolve) {
      var fakebackend = resolve.backend;
      var result = resolve.result;
      // Asserts.
      done();
     })

      @method promiseImport
      @param {String} YAMLBundleURL File to import.
      @param {String} [name] Name of bundle to load, optional when
             only one target in the bundle.
      @param {Object} fakebackend An instance of fakebackend from the
              utils makeFakeBackend() method.
      @return {Promise} Outlined in description.
    */
    promiseImport: function(YAMLBundleURL, name, fakebackend) {
      if (!fakebackend) {
        fakebackend = this.makeFakeBackend();
      }
      var db = fakebackend.db;
      db.environment.set('defaultSeries', 'precise');
      var fixture = jujuTests.utils.loadFixture(YAMLBundleURL);
      return fakebackend.promiseImport(fixture, name)
             .then(function(result) {
               return {result: result, backend: fakebackend};
             });
    }
  });
}, '0.1.0', {
  requires: [
    'handlebars',
    'io',
    'node',
    'promise',
    'json-parse',
    'datasource-local',
    'juju-charm-store',
    'juju-env-fakebackend'
  ]
});
<|endoftext|>


<commit_before>require('mocha');
require('should');
var path = require('path');
var assert = require('assert');
var typeOf = require('kind-of');
var isBuffer = require('is-buffer');
var support = require('./support');
var App = support.resolve();
var List = App.List;
var View = App.View;
var Views = App.Views;
var collection;

describe('views', function () {
  describe('constructor', function () {
    it('should create an instance of Views:', function () {
      var collection = new Views();
      assert(collection instanceof Views);
    });

    it('should instantiate without `new`:', function () {
      var collection = Views();
      assert(collection instanceof Views);
    });
  });

  describe('static methods', function () {
    it('should expose `extend`:', function () {
      assert(typeof Views.extend ==='function');
    });
  });

  describe('prototype methods', function () {
    beforeEach(function() {
      collection = new Views();
    });

    var methods = [
      'use',
      'setView',
      'addView',
      'addViews',
      'addList',
      'getView',
      'constructor',
      'set',
      'get',
      'del',
      'define',
      'visit',
      'on',
      'once',
      'off',
      'emit',
      'listeners',
      'hasListeners'
    ];

    methods.forEach(function (method) {
      it('should expose ' + method + ' method', function () {
        assert(typeof collection[method] === 'function');
      });
    });

    it('should expose isCollection property', function () {
      assert(typeof collection.isCollection === 'boolean');
    });

    it('should expose queue property', function () {
      assert(Array.isArray(collection.queue));
    });

    it('should expose views property', function () {
      assert(typeOf(collection.views) === 'object');
    });

    it('should expose options property', function () {
      assert(typeOf(collection.options) === 'object');
    });
  });

  describe('instance', function () {
    beforeEach(function() {
      collection = new Views();
    });

    it('should set a value on the instance:', function () {
      collection.set('a', 'b');
      assert(collection.a ==='b');
    });

    it('should get a value from the instance:', function () {
      collection.set('a', 'b');
      assert(collection.get('a') ==='b');
    });
  });

  describe('option', function() {
    beforeEach(function() {
      collection = new Views();
    });

    it('should set a key/value pair on options:', function () {
      collection.option('a', 'b');
      assert(collection.options.a === 'b');
    });

    it('should set an object on options:', function () {
      collection.option({c: 'd'});
      assert(collection.options.c === 'd');
    });

    it('should get an option:', function () {
      collection.option({c: 'd'});
      var c = collection.option('c');
      assert(c === 'd');
    });
  });

  describe('addView', function() {
    beforeEach(function() {
      collection = new Views();
    });

    it('should throw an error when args are invalid:', function () {
      (function () {
        collection.addView(function() {});
      }).should.throw('expected value to be an object.');
    });

    it('should add a view to `views`:', function () {
      collection.addView('foo');
      collection.views.should.have.property('foo');

      collection.addView('one', {content: '...'});
      assert(typeof collection.views.one === 'object');
      assert(isBuffer(collection.views.one.contents));
    });

    it('should create an instance of `View`:', function () {
      collection.addView('one', {content: '...'});
      assert(collection.views.one instanceof collection.View);
    });

    it('should allow an `View` constructor to be passed:', function () {
      View.prototype.foo = function(key, value) {
        this[key] = value;
      };
      collection = new Views({View: View});
      collection.addView('one', {content: '...'});
      collection.views.one.foo('bar', 'baz');
      assert(collection.views.one.bar === 'baz');
    });

    it('should allow an instance of `View` to be passed:', function () {
      var collection = new Views({View: View});
      var view = new View({content: '...'});
      collection.addView('one', view);
      view.set('abc', 'xyz');
      assert(collection.views.one instanceof collection.View);
      assert(isBuffer(collection.views.one.contents));
      assert(collection.views.one.abc === 'xyz');
    });
  });

  describe('addViews', function() {
    beforeEach(function() {
      collection = new Views();
    });

    it('should add multiple views:', function () {
      collection.addViews({
        one: {content: 'foo'},
        two: {content: 'bar'}
      });
      assert(isBuffer(collection.views.one.contents));
      assert(isBuffer(collection.views.two.contents));
    });

    it('should create views from an instance of Views', function () {
      collection.addViews({
        one: {content: 'foo'},
        two: {content: 'bar'}
      });
      var pages = new Views(collection);
      assert(isBuffer(pages.views.one.contents));
      assert(isBuffer(pages.views.two.contents));
    });

    it('should add an array of views:', function () {
      collection.addViews([
        {path: 'one', content: 'foo'},
        {path: 'two', content: 'bar'}
      ]);
      assert(isBuffer(collection.views.one.contents));
      assert(isBuffer(collection.views.two.contents));
    });
  });

  describe('view', function() {
    beforeEach(function() {
      collection = new Views();
    });

    it('should return a single collection view from a key-value pair', function () {
      var one = collection.view('one', {content: 'foo'});
      var two = collection.view('two', {content: 'bar'});

      assert(one instanceof View);
      assert(one.path === 'one');
      assert(two instanceof View);
      assert(two.path === 'two');
    });

    it('should return a single collection view from an object', function () {
      var one = collection.view({path: 'one', content: 'foo'});
      var two = collection.view({path: 'two', content: 'bar'});

      assert(one instanceof View);
      assert(one.path === 'one');
      assert(two instanceof View);
      assert(two.path === 'two');
    });
  });

  describe('addList', function() {
    beforeEach(function() {
      collection = new Views();
    });

    it('should add a list of views:', function () {
      collection.addList([
        {path: 'one', content: 'foo'},
        {path: 'two', content: 'bar'}
      ]);
      assert(isBuffer(collection.views.one.contents));
      assert(isBuffer(collection.views.two.contents));
    });

    it('should add a list of views from the constructor:', function () {
      var list = new List([
        {path: 'one', content: 'foo'},
        {path: 'two', content: 'bar'}
      ]);

      collection = new Views(list);
      assert(isBuffer(collection.views.one.contents));
      assert(isBuffer(collection.views.two.contents));
    });

    it('should throw an error when list is not an array:', function () {
      var views = new Views();
      (function () {
        views.addList();
      }).should.throw('expected list to be an array.');

      (function () {
        views.addList({});
      }).should.throw('expected list to be an array.');

      (function () {
        views.addList('foo');
      }).should.throw('expected list to be an array.');
    });

    it('should load an array of items from an event:', function () {
      var collection = new Views();

      collection.on('addList', function (list) {
        while (list.length) {
          collection.addView({path: list.pop()});
        }
        this.loaded = true;
      });

      collection.addList(['a.txt', 'b.txt', 'c.txt']);
      assert(collection.views.hasOwnProperty('a.txt'));
      assert(collection.views['a.txt'].path === 'a.txt');
    });

    it('should load an array of items from the addList callback:', function () {
      var collection = new Views();

      collection.addList(['a.txt', 'b.txt', 'c.txt'], function (fp) {
        return {path: fp};
      });
      assert(collection.views.hasOwnProperty('a.txt'));
      assert(collection.views['a.txt'].path === 'a.txt');
    });

    // it('should not blow up on', function () {
    //   var collection = new Views();
    //   var list = [{path: 'a.txt'}, {path: 'b.txt'}, {path: 'c.txt'}];
    //   collection.addList(list, function (item) {
    //     item.content = path.basename(item.path, path.extname(item.path));
    //   });
    //   assert(collection.views.hasOwnProperty('a.txt'));
    //   assert(collection.views['a.txt'].path === 'a.txt');
    //   assert(collection.views['a.txt'].content === 'a');
    // });

    it('should load an object of views from an event:', function () {
      var collection = new Views();

      collection.on('addViews', function (views) {
        for (var key in views) {
          collection.addView('foo/' + key, views[key]);
          delete views[key];
        }
      });

      collection.addViews({
        a: {path: 'a.txt'},
        b: {path: 'b.txt'},
        c: {path: 'c.txt'}
      });

      assert(collection.views.hasOwnProperty('foo/a'));
      assert(collection.views['foo/a'].path === 'a.txt');
    });

    it('should signal `loaded` when finished:', function () {
      var collection = new Views();

      collection.on('addViews', function (views) {
        for (var key in views) {
          if (key === 'c') break;
          collection.addView('foo/' + key, views[key]);
        }
      });

      collection.addViews({
        a: {path: 'a.txt'},
        b: {path: 'b.txt'},
        c: {path: 'c.txt'}
      });

      assert(collection.views.hasOwnProperty('foo/a'));
      assert(!collection.views.hasOwnProperty('foo/c'));
      assert(collection.views['foo/a'].path === 'a.txt');
    });
  });

  describe('getView', function() {
    beforeEach(function() {
      collection = new Views();
    });
    it('should get a view from `views`:', function () {
      collection.addView('one', {content: 'aaa'});
      collection.addView('two', {content: 'zzz'});
      assert(isBuffer(collection.views.one.contents));
      assert(isBuffer(collection.getView('one').contents));
      assert(collection.getView('one').contents.toString() === 'aaa');
      assert(collection.getView('two').contents.toString() === 'zzz');
    });
  });

  describe('count', function() {
    beforeEach(function() {
      collection = new Views();
    });

    it('should get the number of views:', function () {
      collection.addView('one', {content: 'aaa'});
      collection.addView('two', {content: 'zzz'});
      assert(Object.keys(collection.views).length === 2);
    });
  });
});

describe('options', function() {
  describe('options.renameKey', function() {
    beforeEach(function() {
      collection = new Views({
        renameKey: function (key) {
          return path.basename(key);
        }
      });
    });

    it('should use a custom rename key function on view keys', function() {
      collection.addView('a/b/c/d.hbs', {content: 'foo bar baz'});
      assert(collection.views['d.hbs'].contents.toString() === 'foo bar baz');
    });

    it('should get a view with the renamed key:', function () {
      collection.addView('a/b/c/d.hbs', {content: 'foo bar baz'});
      assert(collection.getView('d.hbs').contents.toString() === 'foo bar baz');
    });

    it('should get a view with the original key:', function () {
      collection.addView('a/b/c/d.hbs', {content: 'foo bar baz'});
      assert(collection.getView('a/b/c/d.hbs').contents.toString() === 'foo bar baz');
    });
  });
});

describe('queue', function () {
  beforeEach(function () {
    collection = new Views();
  });

  it('should emit arguments on addView', function (done) {
    collection.on('addView', function (args) {
      assert(args[0] === 'a');
      assert(args[1] === 'b');
      assert(args[2] === 'c');
      assert(args[3] === 'd');
      assert(args[4] === 'e');
      done();
    });

    collection.addView('a', 'b', 'c', 'd', 'e');
  });

  it('should expose the `queue` property for loading views', function () {
    collection.queue.push(collection.view('b', {path: 'b'}));

    collection.addView('a', {path: 'a'});
    assert(collection.views.hasOwnProperty('a'));
    assert(collection.views.hasOwnProperty('b'));
  });

  it('should load all views on the queue when addView is called', function () {
    collection.on('addView', function (args) {
      var len = args.length;
      var last = args[len - 1];
      if (typeof last === 'string') {
        args[len - 1] = { content: last };
      }
    });

    collection.addView('a.html', 'aaa');
    collection.addView('b.html', 'bbb');
    collection.addView('c.html', 'ccc');

    assert(collection.views.hasOwnProperty('a.html'));
    assert(collection.getView('a.html').content === 'aaa');
    assert(collection.views.hasOwnProperty('b.html'));
    assert(collection.getView('b.html').content === 'bbb');
    assert(collection.views.hasOwnProperty('c.html'));
    assert(collection.getView('c.html').content === 'ccc');
  });
});<commit_msg>use `isView` instead of `instanceof`<commit_after>require('mocha');
require('should');
var path = require('path');
var assert = require('assert');
var typeOf = require('kind-of');
var isBuffer = require('is-buffer');
var support = require('./support');
var App = support.resolve();
var List = App.List;
var View = App.View;
var Views = App.Views;
var collection;

describe('views', function () {
  describe('constructor', function () {
    it('should create an instance of Views:', function () {
      var collection = new Views();
      assert(collection instanceof Views);
    });

    it('should instantiate without `new`:', function () {
      var collection = Views();
      assert(collection instanceof Views);
    });
  });

  describe('static methods', function () {
    it('should expose `extend`:', function () {
      assert(typeof Views.extend ==='function');
    });
  });

  describe('prototype methods', function () {
    beforeEach(function() {
      collection = new Views();
    });

    var methods = [
      'use',
      'setView',
      'addView',
      'addViews',
      'addList',
      'getView',
      'constructor',
      'set',
      'get',
      'del',
      'define',
      'visit',
      'on',
      'once',
      'off',
      'emit',
      'listeners',
      'hasListeners'
    ];

    methods.forEach(function (method) {
      it('should expose ' + method + ' method', function () {
        assert(typeof collection[method] === 'function');
      });
    });

    it('should expose isCollection property', function () {
      assert(typeof collection.isCollection === 'boolean');
    });

    it('should expose queue property', function () {
      assert(Array.isArray(collection.queue));
    });

    it('should expose views property', function () {
      assert(typeOf(collection.views) === 'object');
    });

    it('should expose options property', function () {
      assert(typeOf(collection.options) === 'object');
    });
  });

  describe('instance', function () {
    beforeEach(function() {
      collection = new Views();
    });

    it('should set a value on the instance:', function () {
      collection.set('a', 'b');
      assert(collection.a ==='b');
    });

    it('should get a value from the instance:', function () {
      collection.set('a', 'b');
      assert(collection.get('a') ==='b');
    });
  });

  describe('option', function() {
    beforeEach(function() {
      collection = new Views();
    });

    it('should set a key/value pair on options:', function () {
      collection.option('a', 'b');
      assert(collection.options.a === 'b');
    });

    it('should set an object on options:', function () {
      collection.option({c: 'd'});
      assert(collection.options.c === 'd');
    });

    it('should get an option:', function () {
      collection.option({c: 'd'});
      var c = collection.option('c');
      assert(c === 'd');
    });
  });

  describe('addView', function() {
    beforeEach(function() {
      collection = new Views();
    });

    it('should throw an error when args are invalid:', function () {
      (function () {
        collection.addView(function() {});
      }).should.throw('expected value to be an object.');
    });

    it('should add a view to `views`:', function () {
      collection.addView('foo');
      collection.views.should.have.property('foo');

      collection.addView('one', {content: '...'});
      assert(typeof collection.views.one === 'object');
      assert(isBuffer(collection.views.one.contents));
    });

    it('should create an instance of `View`:', function () {
      collection.addView('one', {content: '...'});
      assert(collection.views.one instanceof collection.View);
    });

    it('should allow an `View` constructor to be passed:', function () {
      View.prototype.foo = function(key, value) {
        this[key] = value;
      };
      collection = new Views({View: View});
      collection.addView('one', {content: '...'});
      collection.views.one.foo('bar', 'baz');
      assert(collection.views.one.bar === 'baz');
    });

    it('should allow an instance of `View` to be passed:', function () {
      var collection = new Views({View: View});
      var view = new View({content: '...'});
      collection.addView('one', view);
      view.set('abc', 'xyz');
      assert(collection.views.one instanceof collection.View);
      assert(isBuffer(collection.views.one.contents));
      assert(collection.views.one.abc === 'xyz');
    });
  });

  describe('addViews', function() {
    beforeEach(function() {
      collection = new Views();
    });

    it('should add multiple views:', function () {
      collection.addViews({
        one: {content: 'foo'},
        two: {content: 'bar'}
      });
      assert(isBuffer(collection.views.one.contents));
      assert(isBuffer(collection.views.two.contents));
    });

    it('should create views from an instance of Views', function () {
      collection.addViews({
        one: {content: 'foo'},
        two: {content: 'bar'}
      });
      var pages = new Views(collection);
      assert(isBuffer(pages.views.one.contents));
      assert(isBuffer(pages.views.two.contents));
    });

    it('should add an array of views:', function () {
      collection.addViews([
        {path: 'one', content: 'foo'},
        {path: 'two', content: 'bar'}
      ]);
      assert(isBuffer(collection.views.one.contents));
      assert(isBuffer(collection.views.two.contents));
    });
  });

  describe('view', function() {
    beforeEach(function() {
      collection = new Views();
    });

    it('should return a single collection view from a key-value pair', function () {
      var one = collection.view('one', {content: 'foo'});
      var two = collection.view('two', {content: 'bar'});

      assert(one.isView);
      assert(one.path === 'one');
      assert(two.isView);
      assert(two.path === 'two');
    });

    it('should return a single collection view from an object', function () {
      var one = collection.view({path: 'one', content: 'foo'});
      var two = collection.view({path: 'two', content: 'bar'});

      assert(one.isView);
      assert(one.path === 'one');
      assert(two.isView);
      assert(two.path === 'two');
    });
  });

  describe('addList', function() {
    beforeEach(function() {
      collection = new Views();
    });

    it('should add a list of views:', function () {
      collection.addList([
        {path: 'one', content: 'foo'},
        {path: 'two', content: 'bar'}
      ]);
      assert(isBuffer(collection.views.one.contents));
      assert(isBuffer(collection.views.two.contents));
    });

    it('should add a list of views from the constructor:', function () {
      var list = new List([
        {path: 'one', content: 'foo'},
        {path: 'two', content: 'bar'}
      ]);

      collection = new Views(list);
      assert(isBuffer(collection.views.one.contents));
      assert(isBuffer(collection.views.two.contents));
    });

    it('should throw an error when list is not an array:', function () {
      var views = new Views();
      (function () {
        views.addList();
      }).should.throw('expected list to be an array.');

      (function () {
        views.addList({});
      }).should.throw('expected list to be an array.');

      (function () {
        views.addList('foo');
      }).should.throw('expected list to be an array.');
    });

    it('should load an array of items from an event:', function () {
      var collection = new Views();

      collection.on('addList', function (list) {
        while (list.length) {
          collection.addView({path: list.pop()});
        }
        this.loaded = true;
      });

      collection.addList(['a.txt', 'b.txt', 'c.txt']);
      assert(collection.views.hasOwnProperty('a.txt'));
      assert(collection.views['a.txt'].path === 'a.txt');
    });

    it('should load an array of items from the addList callback:', function () {
      var collection = new Views();

      collection.addList(['a.txt', 'b.txt', 'c.txt'], function (fp) {
        return {path: fp};
      });
      assert(collection.views.hasOwnProperty('a.txt'));
      assert(collection.views['a.txt'].path === 'a.txt');
    });

    // it('should not blow up on', function () {
    //   var collection = new Views();
    //   var list = [{path: 'a.txt'}, {path: 'b.txt'}, {path: 'c.txt'}];
    //   collection.addList(list, function (item) {
    //     item.content = path.basename(item.path, path.extname(item.path));
    //   });
    //   assert(collection.views.hasOwnProperty('a.txt'));
    //   assert(collection.views['a.txt'].path === 'a.txt');
    //   assert(collection.views['a.txt'].content === 'a');
    // });

    it('should load an object of views from an event:', function () {
      var collection = new Views();

      collection.on('addViews', function (views) {
        for (var key in views) {
          collection.addView('foo/' + key, views[key]);
          delete views[key];
        }
      });

      collection.addViews({
        a: {path: 'a.txt'},
        b: {path: 'b.txt'},
        c: {path: 'c.txt'}
      });

      assert(collection.views.hasOwnProperty('foo/a'));
      assert(collection.views['foo/a'].path === 'a.txt');
    });

    it('should signal `loaded` when finished:', function () {
      var collection = new Views();

      collection.on('addViews', function (views) {
        for (var key in views) {
          if (key === 'c') break;
          collection.addView('foo/' + key, views[key]);
        }
      });

      collection.addViews({
        a: {path: 'a.txt'},
        b: {path: 'b.txt'},
        c: {path: 'c.txt'}
      });

      assert(collection.views.hasOwnProperty('foo/a'));
      assert(!collection.views.hasOwnProperty('foo/c'));
      assert(collection.views['foo/a'].path === 'a.txt');
    });
  });

  describe('getView', function() {
    beforeEach(function() {
      collection = new Views();
    });
    it('should get a view from `views`:', function () {
      collection.addView('one', {content: 'aaa'});
      collection.addView('two', {content: 'zzz'});
      assert(isBuffer(collection.views.one.contents));
      assert(isBuffer(collection.getView('one').contents));
      assert(collection.getView('one').contents.toString() === 'aaa');
      assert(collection.getView('two').contents.toString() === 'zzz');
    });
  });

  describe('count', function() {
    beforeEach(function() {
      collection = new Views();
    });

    it('should get the number of views:', function () {
      collection.addView('one', {content: 'aaa'});
      collection.addView('two', {content: 'zzz'});
      assert(Object.keys(collection.views).length === 2);
    });
  });
});

describe('options', function() {
  describe('options.renameKey', function() {
    beforeEach(function() {
      collection = new Views({
        renameKey: function (key) {
          return path.basename(key);
        }
      });
    });

    it('should use a custom rename key function on view keys', function() {
      collection.addView('a/b/c/d.hbs', {content: 'foo bar baz'});
      assert(collection.views['d.hbs'].contents.toString() === 'foo bar baz');
    });

    it('should get a view with the renamed key:', function () {
      collection.addView('a/b/c/d.hbs', {content: 'foo bar baz'});
      assert(collection.getView('d.hbs').contents.toString() === 'foo bar baz');
    });

    it('should get a view with the original key:', function () {
      collection.addView('a/b/c/d.hbs', {content: 'foo bar baz'});
      assert(collection.getView('a/b/c/d.hbs').contents.toString() === 'foo bar baz');
    });
  });
});

describe('queue', function () {
  beforeEach(function () {
    collection = new Views();
  });

  it('should emit arguments on addView', function (done) {
    collection.on('addView', function (args) {
      assert(args[0] === 'a');
      assert(args[1] === 'b');
      assert(args[2] === 'c');
      assert(args[3] === 'd');
      assert(args[4] === 'e');
      done();
    });

    collection.addView('a', 'b', 'c', 'd', 'e');
  });

  it('should expose the `queue` property for loading views', function () {
    collection.queue.push(collection.view('b', {path: 'b'}));

    collection.addView('a', {path: 'a'});
    assert(collection.views.hasOwnProperty('a'));
    assert(collection.views.hasOwnProperty('b'));
  });

  it('should load all views on the queue when addView is called', function () {
    collection.on('addView', function (args) {
      var len = args.length;
      var last = args[len - 1];
      if (typeof last === 'string') {
        args[len - 1] = { content: last };
      }
    });

    collection.addView('a.html', 'aaa');
    collection.addView('b.html', 'bbb');
    collection.addView('c.html', 'ccc');

    assert(collection.views.hasOwnProperty('a.html'));
    assert(collection.getView('a.html').content === 'aaa');
    assert(collection.views.hasOwnProperty('b.html'));
    assert(collection.getView('b.html').content === 'bbb');
    assert(collection.views.hasOwnProperty('c.html'));
    assert(collection.getView('c.html').content === 'ccc');
  });
});<|endoftext|>


<commit_before>import $ from 'jquery';
import * as _ from 'lodash';
import * as d3 from 'd3';

import clinicalTimelineExports from './timeline-lib';

function plotCaseLabelsInTimeline(caseIds, clinicalDataMap, caseMetaData) {

    var fillColorAndLabelForCase = function (circle, caseId) {
        var label = caseMetaData.label[caseId];
        var color = caseMetaData.color[caseId];
        circle.select("circle").attr("fill", color);
        circle.append("text")
            .attr("y", 4)
            .attr("text-anchor", "middle")
            .attr("font-size", 10)
            .attr("fill", "white")
            .text(label);
    }

    for (var i = 0; i < caseIds.length; i++) {
        var caseId = caseIds[i];
        var clinicalData = clinicalDataMap[caseId];
        var compareAgainstIds = [caseId];

        var OtherSampleId = clinicalData["OTHER_SAMPLE_ID"];
        if (OtherSampleId) {
            compareAgainstIds = compareAgainstIds.concat(OtherSampleId);
        }
        var circle = d3.selectAll(".timelineSeries_0").filter(function (x) {
            if (x.tooltip_tables.length === 1) {
                var specRefNum = x.tooltip_tables[0].filter(function (x) {
                    return x[0] === "SpecimenReferenceNumber" || x[0] === "SPECIMEN_REFERENCE_NUMBER" || x[0] === "SAMPLE_ID";
                })[0];
                if (specRefNum) {
                    return compareAgainstIds.indexOf(specRefNum[1]) !== -1;
                }
            }
            return undefined;
        });
        if (circle[0][0]) {
            var g = document.createElementNS("http://www.w3.org/2000/svg", "g");
            $(g).attr("transform", "translate(" + circle.attr("cx") + "," + circle.attr("cy") + ")");
            $(circle[0]).removeAttr("cx");
            $(circle[0]).removeAttr("cy");
            $(circle[0]).removeAttr("style");
            $(circle[0]).qtip('destroy');
            $(circle[0]).unbind('mouseover mouseout');
            $(circle[0]).wrap(g);
            g = $(circle[0]).parent();
            g.prop("__data__", $(circle[0]).prop("__data__"));
            fillColorAndLabelForCase(d3.select(g.get(0)), caseId);
            window.pvTimeline.addDataPointTooltip(g);
        }
    }
}

export function buildTimeline(params, caseIds, patientInfo, clinicalDataMap, caseMetaData, data, width) {

    if (data.length === 0) return;

    var timeData = clinicalTimelineExports.clinicalTimelineParser(data);
    if (timeData.length === 0) return;

    // order specimens by svg label number
    var specimen = _.find(timeData, (item) => item.label === 'Specimen');
    if (specimen) {
        specimen.times = _.sortBy(specimen.times, function (x) {
            var sortOrder = Infinity;

            var specRefNum = _.filter(x.tooltip_tables[0], function (x) {
                return x[0] === "SPECIMEN_REFERENCE_NUMBER" || x[0] === "SpecimenReferenceNumber" || x[0] === "SAMPLE_ID";
            });
            if (specRefNum) {
                if (specRefNum.length > 1) {
                    console.warn("More than 1 specimen reference number found in tooltip table");
                } else if (specRefNum.length === 1) {
                    sortOrder = caseIds.indexOf(specRefNum[0][1]);
                    if (sortOrder === -1) {
                        sortOrder = Infinity;
                    }
                }
            }
            return sortOrder;
        });
    }

    // add DECEASED point to Status track using data from
    // .*OS_MONTHS$ if .*OS_STATUS$ is DECEASED
    var i;
    var prefixes;
    prefixes = Object.keys(patientInfo).filter(function (x) {
        // find all keys postfixed with "OS_STATUS"
        return /OS_STATUS$/.test(x);
    }).map(function (x) {
        // get the prefixes
        return x.substr(0, x.length - "OS_STATUS".length);
    });

    for (i = 0; i < prefixes.length; i++) {
        var prefix = prefixes[i];
        if (patientInfo[prefix + "OS_STATUS"] === "DECEASED" &&
            prefix + "OS_MONTHS" in patientInfo) {
            var days = Math.round(parseFloat(patientInfo[prefix + "OS_MONTHS"]) * 30.4);
            var timePoint = {
                "starting_time": days,
                "ending_time": days,
                "display": "circle",
                "color": "#000",
                "tooltip_tables": [
                    [
                        ["START_DATE", days],
                        ["STATUS", "DECEASED"]
                    ]
                ]
            }

            var trackData = timeData.filter(function (x) {
                return x.label === "Status";
            })[0];

            if (trackData) {
                trackData.times = trackData.times.concat(timePoint);
            } else {
                timeData = timeData.concat({
                    "label": "Status",
                    "times": [timePoint]
                });
            }
            // Add timepoint only once in case of multiple prefixes
            break;
        }
    }

    window.pvTimeline = clinicalTimelineExports.clinicalTimeline()
        .width(width)
        .data(timeData)
        .divId("#timeline")
        .setTimepointsDisplay("Imaging", "square")
        .orderTracks(["Specimen", "Surgery", "Status", "Diagnostics", "Diagnostic", "Imaging", "Lab_test", "Treatment"])
        .splitByClinicalAttributes("Lab_test", "TEST")
    var splitData = window.pvTimeline.data();
    // Get TEST names that have a RESULT field in their clinical
    // tooltip table. We assume the RESULT field contains
    // integer/float values that can be used to size the dots on the
    // timeline by
    var testsWithResults = splitData.filter(function (x) {
        return x.parent_track === 'Lab_test' &&
            _.every(x.times.map(
                function (t) {
                    return t.tooltip_tables.length === 1 &&
                        t.tooltip_tables[0].filter(function (a) {
                            return a[0] === 'RESULT'
                        }).length > 0;
                })
            )
    }).map(function (x) {
        return x.label;
    });
    // Scale dot size on timepoint by RESULT field
    testsWithResults.forEach(function (test) {
        window.pvTimeline =
            window.pvTimeline
                .sizeByClinicalAttribute(test, "RESULT")
    })
    window.pvTimeline =
        window.pvTimeline
            .splitByClinicalAttributes("Treatment", ["TREATMENT_TYPE","SUBTYPE", "AGENT"])
            .collapseAll()
            .toggleTrackCollapse("Specimen")
            .enableTrackTooltips(false)
            .plugins([{obj: new clinicalTimelineExports.trimClinicalTimeline("Trim Timeline"), enabled: true}])
            .addPostTimelineHook(plotCaseLabelsInTimeline.bind(this, caseIds, clinicalDataMap, caseMetaData));
    window.pvTimeline();
    $("#timeline-container").show();

    // $.post("http://www.cbioportal.org/clinical_timeline_data.json",
    //     params,
    //
    //     , "json"
    // );

}
<commit_msg>Clinical timeline: Create split heirarchy for Diagnosis subtypes<commit_after>import $ from 'jquery';
import * as _ from 'lodash';
import * as d3 from 'd3';

import clinicalTimelineExports from './timeline-lib';

function plotCaseLabelsInTimeline(caseIds, clinicalDataMap, caseMetaData) {

    var fillColorAndLabelForCase = function (circle, caseId) {
        var label = caseMetaData.label[caseId];
        var color = caseMetaData.color[caseId];
        circle.select("circle").attr("fill", color);
        circle.append("text")
            .attr("y", 4)
            .attr("text-anchor", "middle")
            .attr("font-size", 10)
            .attr("fill", "white")
            .text(label);
    }

    for (var i = 0; i < caseIds.length; i++) {
        var caseId = caseIds[i];
        var clinicalData = clinicalDataMap[caseId];
        var compareAgainstIds = [caseId];

        var OtherSampleId = clinicalData["OTHER_SAMPLE_ID"];
        if (OtherSampleId) {
            compareAgainstIds = compareAgainstIds.concat(OtherSampleId);
        }
        var circle = d3.selectAll(".timelineSeries_0").filter(function (x) {
            if (x.tooltip_tables.length === 1) {
                var specRefNum = x.tooltip_tables[0].filter(function (x) {
                    return x[0] === "SpecimenReferenceNumber" || x[0] === "SPECIMEN_REFERENCE_NUMBER" || x[0] === "SAMPLE_ID";
                })[0];
                if (specRefNum) {
                    return compareAgainstIds.indexOf(specRefNum[1]) !== -1;
                }
            }
            return undefined;
        });
        if (circle[0][0]) {
            var g = document.createElementNS("http://www.w3.org/2000/svg", "g");
            $(g).attr("transform", "translate(" + circle.attr("cx") + "," + circle.attr("cy") + ")");
            $(circle[0]).removeAttr("cx");
            $(circle[0]).removeAttr("cy");
            $(circle[0]).removeAttr("style");
            $(circle[0]).qtip('destroy');
            $(circle[0]).unbind('mouseover mouseout');
            $(circle[0]).wrap(g);
            g = $(circle[0]).parent();
            g.prop("__data__", $(circle[0]).prop("__data__"));
            fillColorAndLabelForCase(d3.select(g.get(0)), caseId);
            window.pvTimeline.addDataPointTooltip(g);
        }
    }
}

export function buildTimeline(params, caseIds, patientInfo, clinicalDataMap, caseMetaData, data, width) {

    if (data.length === 0) return;

    var timeData = clinicalTimelineExports.clinicalTimelineParser(data);
    if (timeData.length === 0) return;

    // order specimens by svg label number
    var specimen = _.find(timeData, (item) => item.label === 'Specimen');
    if (specimen) {
        specimen.times = _.sortBy(specimen.times, function (x) {
            var sortOrder = Infinity;

            var specRefNum = _.filter(x.tooltip_tables[0], function (x) {
                return x[0] === "SPECIMEN_REFERENCE_NUMBER" || x[0] === "SpecimenReferenceNumber" || x[0] === "SAMPLE_ID";
            });
            if (specRefNum) {
                if (specRefNum.length > 1) {
                    console.warn("More than 1 specimen reference number found in tooltip table");
                } else if (specRefNum.length === 1) {
                    sortOrder = caseIds.indexOf(specRefNum[0][1]);
                    if (sortOrder === -1) {
                        sortOrder = Infinity;
                    }
                }
            }
            return sortOrder;
        });
    }

    // add DECEASED point to Status track using data from
    // .*OS_MONTHS$ if .*OS_STATUS$ is DECEASED
    var i;
    var prefixes;
    prefixes = Object.keys(patientInfo).filter(function (x) {
        // find all keys postfixed with "OS_STATUS"
        return /OS_STATUS$/.test(x);
    }).map(function (x) {
        // get the prefixes
        return x.substr(0, x.length - "OS_STATUS".length);
    });

    for (i = 0; i < prefixes.length; i++) {
        var prefix = prefixes[i];
        if (patientInfo[prefix + "OS_STATUS"] === "DECEASED" &&
            prefix + "OS_MONTHS" in patientInfo) {
            var days = Math.round(parseFloat(patientInfo[prefix + "OS_MONTHS"]) * 30.4);
            var timePoint = {
                "starting_time": days,
                "ending_time": days,
                "display": "circle",
                "color": "#000",
                "tooltip_tables": [
                    [
                        ["START_DATE", days],
                        ["STATUS", "DECEASED"]
                    ]
                ]
            }

            var trackData = timeData.filter(function (x) {
                return x.label === "Status";
            })[0];

            if (trackData) {
                trackData.times = trackData.times.concat(timePoint);
            } else {
                timeData = timeData.concat({
                    "label": "Status",
                    "times": [timePoint]
                });
            }
            // Add timepoint only once in case of multiple prefixes
            break;
        }
    }

    window.pvTimeline = clinicalTimelineExports.clinicalTimeline()
        .width(width)
        .data(timeData)
        .divId("#timeline")
        .setTimepointsDisplay("Imaging", "square")
        .orderTracks(["Specimen", "Surgery", "Status", "Diagnostics", "Diagnostic", "Imaging", "Lab_test", "Treatment"])
        .splitByClinicalAttributes("Lab_test", "TEST")
    var splitData = window.pvTimeline.data();
    // Get TEST names that have a RESULT field in their clinical
    // tooltip table. We assume the RESULT field contains
    // integer/float values that can be used to size the dots on the
    // timeline by
    var testsWithResults = splitData.filter(function (x) {
        return x.parent_track === 'Lab_test' &&
            _.every(x.times.map(
                function (t) {
                    return t.tooltip_tables.length === 1 &&
                        t.tooltip_tables[0].filter(function (a) {
                            return a[0] === 'RESULT'
                        }).length > 0;
                })
            )
    }).map(function (x) {
        return x.label;
    });
    // Scale dot size on timepoint by RESULT field
    testsWithResults.forEach(function (test) {
        window.pvTimeline =
            window.pvTimeline
                .sizeByClinicalAttribute(test, "RESULT")
    })
    window.pvTimeline =
        window.pvTimeline
            .splitByClinicalAttributes("Treatment", ["TREATMENT_TYPE","SUBTYPE", "AGENT"])
            .splitByClinicalAttributes("Diagnosis", ["SUBTYPE"])
            .collapseAll()
            .toggleTrackCollapse("Specimen")
            .enableTrackTooltips(false)
            .plugins([{obj: new clinicalTimelineExports.trimClinicalTimeline("Trim Timeline"), enabled: true}])
            .addPostTimelineHook(plotCaseLabelsInTimeline.bind(this, caseIds, clinicalDataMap, caseMetaData));
    window.pvTimeline();
    $("#timeline-container").show();

    // $.post("http://www.cbioportal.org/clinical_timeline_data.json",
    //     params,
    //
    //     , "json"
    // );

}
<|endoftext|>


<commit_before>/*
 * QUnit - jQuery unit testrunner
 *
 * http://docs.jquery.com/QUnit
 *
 * Copyright (c) 2008 John Resig, Jörn Zaefferer
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 * $Id$
 */

var _config = {
	fixture: null,
	Test: [],
	stats: {
		all: 0,
		bad: 0
	},
	queue: [],
	blocking: true,
	timeout: null,
	expected: null,
	currentModule: null,
	asyncTimeout: 2 // seconds for async timeout
};

_config.filters = location.search.length > 1 && //restrict modules/tests by get parameters
		jQuery.map( location.search.slice(1).split('&'), decodeURIComponent );

var isLocal = !!(window.location.protocol == 'file:');

jQuery(function() {
	jQuery('#userAgent').html(navigator.userAgent);
	runTest();
});

function synchronize(callback) {
	_config.queue[_config.queue.length] = callback;
	if(!_config.blocking) {
		process();
	}
}

function process() {
	while(_config.queue.length && !_config.blocking) {
		var call = _config.queue[0];
		_config.queue = _config.queue.slice(1);
		call();
	}
}

function stop(allowFailure) {
	_config.blocking = true;
	var handler = allowFailure ? start : function() {
		ok( false, "Test timed out" );
		start();
	};
	// Disabled, caused too many random errors
	//_config.timeout = setTimeout(handler, _config.asyncTimeout * 1000);
}
function start() {
	// A slight delay, to avoid any current callbacks
	setTimeout(function(){
		if(_config.timeout)
			clearTimeout(_config.timeout);
		_config.blocking = false;
		process();
	}, 13);
}

function validTest( name ) {
	var filters = _config.filters;
	if( !filters )
		return true;

	var i = filters.length,
		run = false;
	while( i-- ){
		var filter = filters[i],
			not = filter.charAt(0) == '!';
		if( not )
			filter = filter.slice(1);
		if( name.indexOf(filter) != -1 )
			return !not;
		if( not )
			run = true;
	}
	return run;
}

function runTest() {
	_config.blocking = false;
	var started = +new Date;
	_config.fixture = document.getElementById('main').innerHTML;
	_config.ajaxSettings = jQuery.ajaxSettings;
	synchronize(function() {
		jQuery('
').html(['Tests completed in ',
			+new Date - started, ' milliseconds.
',
			'', _config.stats.bad, ' tests of ', _config.stats.all, ' failed.
']
			.join(''))
			.appendTo("body");
		jQuery("#banner").addClass(_config.stats.bad ? "fail" : "pass");
	});
}

function test(name, callback, nowait) {
	if(_config.currentModule)
		name = _config.currentModule + " module: " + name;

	if ( !validTest(name) )
		return;

	synchronize(function() {
		_config.Test = [];
		try {
			callback();
		} catch(e) {
			if( typeof console != "undefined" && console.error && console.warn ) {
				console.error("Test " + name + " died, exception and test follows");
				console.error(e);
				console.warn(callback.toString());
			}
			_config.Test.push( [ false, "Died on test #" + (_config.Test.length+1) + ": " + e.message ] );
		}
	});
	synchronize(function() {
		try {
			reset();
		} catch(e) {
			if( typeof console != "undefined" && console.error && console.warn ) {
				console.error("reset() failed, following Test " + name + ", exception and reset fn follows");
				console.error(e);
				console.warn(reset.toString());
			}
		}

		// don't output pause tests
		if(nowait) return;

		if(_config.expected && _config.expected != _config.Test.length) {
			_config.Test.push( [ false, "Expected " + _config.expected + " assertions, but " + _config.Test.length + " were run" ] );
		}
		_config.expected = null;

		var good = 0, bad = 0;
		var ol = document.createElement("ol");
		ol.style.display = "none";
		var li = "", state = "pass";
		for ( var i = 0; i < _config.Test.length; i++ ) {
			var li = document.createElement("li");
			li.className = _config.Test[i][0] ? "pass" : "fail";
			li.appendChild( document.createTextNode(_config.Test[i][1]) );
			ol.appendChild( li );

			_config.stats.all++;
			if ( !_config.Test[i][0] ) {
				state = "fail";
				bad++;
				_config.stats.bad++;
			} else good++;
		}

		var li = document.createElement("li");
		li.className = state;

		var b = document.createElement("strong");
		b.innerHTML = name + " (" + bad + ", " + good + ", " + _config.Test.length + ")";
		b.onclick = function(){
			var n = this.nextSibling;
			if ( jQuery.css( n, "display" ) == "none" )
				n.style.display = "block";
			else
				n.style.display = "none";
		};
		jQuery(b).dblclick(function(event) {
			var target = jQuery(event.target).filter("strong").clone();
			if ( target.length ) {
				target.children().remove();
				location.href = location.href.match(/^(.+?)(\?.*)?$/)[1] + "?" + encodeURIComponent(jQuery.trim(target.text()));
			}
		});
		li.appendChild( b );
		li.appendChild( ol );

		document.getElementById("tests").appendChild( li );
	});
}

// call on start of module test to prepend name to all tests
function module(moduleName) {
	_config.currentModule = moduleName;
}

/**
 * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
 */
function expect(asserts) {
	_config.expected = asserts;
}

/**
 * Resets the test setup. Useful for tests that modify the DOM.
 */
function reset() {
	jQuery("#main").html( _config.fixture );
	jQuery.event.global = {};
	jQuery.ajaxSettings = jQuery.extend({}, _config.ajaxSettings);
}

/**
 * Asserts true.
 * @example ok( jQuery("a").size() > 5, "There must be at least 5 anchors" );
 */
function ok(a, msg) {
	_config.Test.push( [ !!a, msg ] );
}

/**
 * Asserts that two arrays are the same
 */
function isSet(a, b, msg) {
	var ret = true;
	if ( a && b && a.length != undefined && a.length == b.length ) {
		for ( var i = 0; i < a.length; i++ )
			if ( a[i] != b[i] )
				ret = false;
	} else
		ret = false;
	if ( !ret )
		_config.Test.push( [ ret, msg + " expected: " + serialArray(b) + " result: " + serialArray(a) ] );
	else
		_config.Test.push( [ ret, msg ] );
}

/**
 * Asserts that two objects are equivalent
 */
function isObj(a, b, msg) {
	var ret = true;

	if ( a && b ) {
		for ( var i in a )
			if ( a[i] != b[i] )
				ret = false;

		for ( i in b )
			if ( a[i] != b[i] )
				ret = false;
	} else
		ret = false;

    _config.Test.push( [ ret, msg ] );
}

function serialArray( a ) {
	var r = [];

	if ( a && a.length )
        for ( var i = 0; i < a.length; i++ ) {
            var str = a[i].nodeName;
            if ( str ) {
                str = str.toLowerCase();
                if ( a[i].id )
                    str += "#" + a[i].id;
            } else
                str = a[i];
            r.push( str );
        }

	return "[ " + r.join(", ") + " ]";
}

function flatMap(a, block) {
	var result = [];
	$.each(a, function() {
		var x = block.apply(this, arguments);
		if (x !== false)
			result.push(x);
	})
	return result;
}

function serialObject( a ) {
	return "{ " + flatMap(a, function(key, value) {
		return key + ": " + value;
	}).join(", ") + " }";
}

function compare(a, b, msg) {
	var ret = true;
	if ( a && b && a.length != undefined && a.length == b.length ) {
		for ( var i = 0; i < a.length; i++ )
			for(var key in a[i]) {
				if (a[i][key].constructor == Array) {
					for (var arrayKey in a[i][key]) {
						if (a[i][key][arrayKey] != b[i][key][arrayKey]) {
							ret = false;
						}
					}
				} else if (a[i][key] != b[i][key]) {
					ret = false
				}
			}
	} else
		ret = false;
	ok( ret, msg + " expected: " + serialArray(b) + " result: " + serialArray(a) );
}

function compare2(a, b, msg) {
	var ret = true;
	if ( a && b ) {
		for(var key in a) {
			if (a[key].constructor == Array) {
				for (var arrayKey in a[key]) {
					if (a[key][arrayKey] != b[key][arrayKey]) {
						ret = false;
					}
				}
			} else if (a[key] != b[key]) {
				ret = false
			}
		}
		for(key in b) {
			if (b[key].constructor == Array) {
				for (var arrayKey in b[key]) {
					if (a[key][arrayKey] != b[key][arrayKey]) {
						ret = false;
					}
				}
			} else if (a[key] != b[key]) {
				ret = false
			}
		}
	} else
		ret = false;
	ok( ret, msg + " expected: " + serialObject(b) + " result: " + serialObject(a) );
}

/**
 * Returns an array of elements with the given IDs, eg.
 * @example q("main", "foo", "bar")
 * @result [
, , <input id="bar">]
 */
function q() {
	var r = [];
	for ( var i = 0; i < arguments.length; i++ )
		r.push( document.getElementById( arguments[i] ) );
	return r;
}

/**
 * Asserts that a select matches the given IDs
 * @example t("Check for something", "//[a]", ["foo", "baar"]);
 * @result returns true if "//[a]" return two elements with the IDs 'foo' and 'baar'
 */
function t(a,b,c) {
	var f = jQuery(b);
	var s = "";
	for ( var i = 0; i < f.length; i++ )
		s += (s && ",") + '"' + f[i].id + '"';
	isSet(f, q.apply(q,c), a + " (" + b + ")");
}

/**
 * Add random number to url to stop IE from caching
 *
 * @example url("data/test.html")
 * @result "data/test.html?10538358428943"
 *
 * @example url("data/test.php?foo=bar")
 * @result "data/test.php?foo=bar&10538358345554"
 */
function url(value) {
	return value + (/\?/.test(value) ? "&" : "?") + new Date().getTime() + "" + parseInt(Math.random()*100000);
}

/**
 * Checks that the first two arguments are equal, with an optional message.
 * Prints out both actual and expected values.
 *
 * Prefered to ok( actual == expected, message )
 *
 * @example equals( $.format("Expected {0} characters.", 2), "Expected 2 characters." );
 *
 * @param Object actual
 * @param Object expected
 * @param String message (optional)
 */
function equals(actual, expected, message) {
	var result = expected == actual;
	message = message || (result ? "okay" : "failed");
	_config.Test.push( [ result, result ? message + ": " + expected : message + " expected: " + expected + " actual: " + actual ] );
}

/**
 * Trigger an event on an element.
 *
 * @example triggerEvent( document.body, "click" );
 *
 * @param DOMElement elem
 * @param String type
 */
function triggerEvent( elem, type, event ) {
	if ( jQuery.browser.mozilla || jQuery.browser.opera ) {
		event = document.createEvent("MouseEvents");
		event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
			0, 0, 0, 0, 0, false, false, false, false, 0, null);
		elem.dispatchEvent( event );
	} else if ( jQuery.browser.msie ) {
		elem.fireEvent("on"+type);
	}
}
<commit_msg>qunit: modified example for equals to avoid confusion with parameter ordering<commit_after>/*
 * QUnit - jQuery unit testrunner
 *
 * http://docs.jquery.com/QUnit
 *
 * Copyright (c) 2008 John Resig, Jörn Zaefferer
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 * $Id$
 */

var _config = {
	fixture: null,
	Test: [],
	stats: {
		all: 0,
		bad: 0
	},
	queue: [],
	blocking: true,
	timeout: null,
	expected: null,
	currentModule: null,
	asyncTimeout: 2 // seconds for async timeout
};

_config.filters = location.search.length > 1 && //restrict modules/tests by get parameters
		jQuery.map( location.search.slice(1).split('&'), decodeURIComponent );

var isLocal = !!(window.location.protocol == 'file:');

jQuery(function() {
	jQuery('#userAgent').html(navigator.userAgent);
	runTest();
});

function synchronize(callback) {
	_config.queue[_config.queue.length] = callback;
	if(!_config.blocking) {
		process();
	}
}

function process() {
	while(_config.queue.length && !_config.blocking) {
		var call = _config.queue[0];
		_config.queue = _config.queue.slice(1);
		call();
	}
}

function stop(allowFailure) {
	_config.blocking = true;
	var handler = allowFailure ? start : function() {
		ok( false, "Test timed out" );
		start();
	};
	// Disabled, caused too many random errors
	//_config.timeout = setTimeout(handler, _config.asyncTimeout * 1000);
}
function start() {
	// A slight delay, to avoid any current callbacks
	setTimeout(function(){
		if(_config.timeout)
			clearTimeout(_config.timeout);
		_config.blocking = false;
		process();
	}, 13);
}

function validTest( name ) {
	var filters = _config.filters;
	if( !filters )
		return true;

	var i = filters.length,
		run = false;
	while( i-- ){
		var filter = filters[i],
			not = filter.charAt(0) == '!';
		if( not )
			filter = filter.slice(1);
		if( name.indexOf(filter) != -1 )
			return !not;
		if( not )
			run = true;
	}
	return run;
}

function runTest() {
	_config.blocking = false;
	var started = +new Date;
	_config.fixture = document.getElementById('main').innerHTML;
	_config.ajaxSettings = jQuery.ajaxSettings;
	synchronize(function() {
		jQuery('
').html(['Tests completed in ',
			+new Date - started, ' milliseconds.
',
			'', _config.stats.bad, ' tests of ', _config.stats.all, ' failed.
']
			.join(''))
			.appendTo("body");
		jQuery("#banner").addClass(_config.stats.bad ? "fail" : "pass");
	});
}

function test(name, callback, nowait) {
	if(_config.currentModule)
		name = _config.currentModule + " module: " + name;

	if ( !validTest(name) )
		return;

	synchronize(function() {
		_config.Test = [];
		try {
			callback();
		} catch(e) {
			if( typeof console != "undefined" && console.error && console.warn ) {
				console.error("Test " + name + " died, exception and test follows");
				console.error(e);
				console.warn(callback.toString());
			}
			_config.Test.push( [ false, "Died on test #" + (_config.Test.length+1) + ": " + e.message ] );
		}
	});
	synchronize(function() {
		try {
			reset();
		} catch(e) {
			if( typeof console != "undefined" && console.error && console.warn ) {
				console.error("reset() failed, following Test " + name + ", exception and reset fn follows");
				console.error(e);
				console.warn(reset.toString());
			}
		}

		// don't output pause tests
		if(nowait) return;

		if(_config.expected && _config.expected != _config.Test.length) {
			_config.Test.push( [ false, "Expected " + _config.expected + " assertions, but " + _config.Test.length + " were run" ] );
		}
		_config.expected = null;

		var good = 0, bad = 0;
		var ol = document.createElement("ol");
		ol.style.display = "none";
		var li = "", state = "pass";
		for ( var i = 0; i < _config.Test.length; i++ ) {
			var li = document.createElement("li");
			li.className = _config.Test[i][0] ? "pass" : "fail";
			li.appendChild( document.createTextNode(_config.Test[i][1]) );
			ol.appendChild( li );

			_config.stats.all++;
			if ( !_config.Test[i][0] ) {
				state = "fail";
				bad++;
				_config.stats.bad++;
			} else good++;
		}

		var li = document.createElement("li");
		li.className = state;

		var b = document.createElement("strong");
		b.innerHTML = name + " (" + bad + ", " + good + ", " + _config.Test.length + ")";
		b.onclick = function(){
			var n = this.nextSibling;
			if ( jQuery.css( n, "display" ) == "none" )
				n.style.display = "block";
			else
				n.style.display = "none";
		};
		jQuery(b).dblclick(function(event) {
			var target = jQuery(event.target).filter("strong").clone();
			if ( target.length ) {
				target.children().remove();
				location.href = location.href.match(/^(.+?)(\?.*)?$/)[1] + "?" + encodeURIComponent(jQuery.trim(target.text()));
			}
		});
		li.appendChild( b );
		li.appendChild( ol );

		document.getElementById("tests").appendChild( li );
	});
}

// call on start of module test to prepend name to all tests
function module(moduleName) {
	_config.currentModule = moduleName;
}

/**
 * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
 */
function expect(asserts) {
	_config.expected = asserts;
}

/**
 * Resets the test setup. Useful for tests that modify the DOM.
 */
function reset() {
	jQuery("#main").html( _config.fixture );
	jQuery.event.global = {};
	jQuery.ajaxSettings = jQuery.extend({}, _config.ajaxSettings);
}

/**
 * Asserts true.
 * @example ok( jQuery("a").size() > 5, "There must be at least 5 anchors" );
 */
function ok(a, msg) {
	_config.Test.push( [ !!a, msg ] );
}

/**
 * Asserts that two arrays are the same
 */
function isSet(a, b, msg) {
	var ret = true;
	if ( a && b && a.length != undefined && a.length == b.length ) {
		for ( var i = 0; i < a.length; i++ )
			if ( a[i] != b[i] )
				ret = false;
	} else
		ret = false;
	if ( !ret )
		_config.Test.push( [ ret, msg + " expected: " + serialArray(b) + " result: " + serialArray(a) ] );
	else
		_config.Test.push( [ ret, msg ] );
}

/**
 * Asserts that two objects are equivalent
 */
function isObj(a, b, msg) {
	var ret = true;

	if ( a && b ) {
		for ( var i in a )
			if ( a[i] != b[i] )
				ret = false;

		for ( i in b )
			if ( a[i] != b[i] )
				ret = false;
	} else
		ret = false;

    _config.Test.push( [ ret, msg ] );
}

function serialArray( a ) {
	var r = [];

	if ( a && a.length )
        for ( var i = 0; i < a.length; i++ ) {
            var str = a[i].nodeName;
            if ( str ) {
                str = str.toLowerCase();
                if ( a[i].id )
                    str += "#" + a[i].id;
            } else
                str = a[i];
            r.push( str );
        }

	return "[ " + r.join(", ") + " ]";
}

function flatMap(a, block) {
	var result = [];
	$.each(a, function() {
		var x = block.apply(this, arguments);
		if (x !== false)
			result.push(x);
	})
	return result;
}

function serialObject( a ) {
	return "{ " + flatMap(a, function(key, value) {
		return key + ": " + value;
	}).join(", ") + " }";
}

function compare(a, b, msg) {
	var ret = true;
	if ( a && b && a.length != undefined && a.length == b.length ) {
		for ( var i = 0; i < a.length; i++ )
			for(var key in a[i]) {
				if (a[i][key].constructor == Array) {
					for (var arrayKey in a[i][key]) {
						if (a[i][key][arrayKey] != b[i][key][arrayKey]) {
							ret = false;
						}
					}
				} else if (a[i][key] != b[i][key]) {
					ret = false
				}
			}
	} else
		ret = false;
	ok( ret, msg + " expected: " + serialArray(b) + " result: " + serialArray(a) );
}

function compare2(a, b, msg) {
	var ret = true;
	if ( a && b ) {
		for(var key in a) {
			if (a[key].constructor == Array) {
				for (var arrayKey in a[key]) {
					if (a[key][arrayKey] != b[key][arrayKey]) {
						ret = false;
					}
				}
			} else if (a[key] != b[key]) {
				ret = false
			}
		}
		for(key in b) {
			if (b[key].constructor == Array) {
				for (var arrayKey in b[key]) {
					if (a[key][arrayKey] != b[key][arrayKey]) {
						ret = false;
					}
				}
			} else if (a[key] != b[key]) {
				ret = false
			}
		}
	} else
		ret = false;
	ok( ret, msg + " expected: " + serialObject(b) + " result: " + serialObject(a) );
}

/**
 * Returns an array of elements with the given IDs, eg.
 * @example q("main", "foo", "bar")
 * @result [
, , <input id="bar">]
 */
function q() {
	var r = [];
	for ( var i = 0; i < arguments.length; i++ )
		r.push( document.getElementById( arguments[i] ) );
	return r;
}

/**
 * Asserts that a select matches the given IDs
 * @example t("Check for something", "//[a]", ["foo", "baar"]);
 * @result returns true if "//[a]" return two elements with the IDs 'foo' and 'baar'
 */
function t(a,b,c) {
	var f = jQuery(b);
	var s = "";
	for ( var i = 0; i < f.length; i++ )
		s += (s && ",") + '"' + f[i].id + '"';
	isSet(f, q.apply(q,c), a + " (" + b + ")");
}

/**
 * Add random number to url to stop IE from caching
 *
 * @example url("data/test.html")
 * @result "data/test.html?10538358428943"
 *
 * @example url("data/test.php?foo=bar")
 * @result "data/test.php?foo=bar&10538358345554"
 */
function url(value) {
	return value + (/\?/.test(value) ? "&" : "?") + new Date().getTime() + "" + parseInt(Math.random()*100000);
}

/**
 * Checks that the first two arguments are equal, with an optional message.
 * Prints out both actual and expected values.
 *
 * Prefered to ok( actual == expected, message )
 *
 * @example equals( $.format("Received {0} bytes.", 2), "Received 2 bytes." );
 *
 * @param Object actual
 * @param Object expected
 * @param String message (optional)
 */
function equals(actual, expected, message) {
	var result = expected == actual;
	message = message || (result ? "okay" : "failed");
	_config.Test.push( [ result, result ? message + ": " + expected : message + " expected: " + expected + " actual: " + actual ] );
}

/**
 * Trigger an event on an element.
 *
 * @example triggerEvent( document.body, "click" );
 *
 * @param DOMElement elem
 * @param String type
 */
function triggerEvent( elem, type, event ) {
	if ( jQuery.browser.mozilla || jQuery.browser.opera ) {
		event = document.createEvent("MouseEvents");
		event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
			0, 0, 0, 0, 0, false, false, false, false, 0, null);
		elem.dispatchEvent( event );
	} else if ( jQuery.browser.msie ) {
		elem.fireEvent("on"+type);
	}
}
<|endoftext|>


<commit_before>import React from 'react';

import { niseko, HawkCorrigan, Draenal, TheJigglr } from 'CONTRIBUTORS';
import SPELLS from 'common/SPELLS';
import SpellLink from 'common/SpellLink';
import { change, date } from 'common/changelog';

export default [
  change(date(2020, 8, 28), <>First go at removing obsolete Spells and Azerite.</>, [HawkCorrigan]),
  change(date(2020, 3, 17), <>Adjust wording of downtime suggestion. Cancelled casts don't consume a gcd.</>, [Draenal]),
  change(date(2020, 2, 28), <>Added a statistic for <SpellLink id={SPELLS.ANCESTRAL_RESONANCE.id} />.</>, [niseko]),
  change(date(2019, 11, 29), <>Add a statistic counting the amount of maelstrom not wasted due to <SpellLink id={SPELLS.CALL_THE_THUNDER_TALENT.id} />.</>, [Draenal]),
  change(date(2019, 11, 28), <>Added a statistic for <SpellLink id={SPELLS.NATURAL_HARMONY_TRAIT.id} /> to track avg crit, haste, mastery and uptime for each</>, [Draenal]),
  change(date(2019, 11, 27), <>Add an <SpellLink id={SPELLS.EARTH_SHIELD.id} /> statistic to track uptime and healing.</>, [Draenal]),
  change(date(2019, 10, 31), <>Add a checklist item for <SpellLink id={SPELLS.ASCENDANCE_TALENT_ELEMENTAL.id} /></>, [HawkCorrigan] ),
  change(date(2019, 10, 31), <>Fixed formatting for SoP suggestion</>, [HawkCorrigan]),
  change(date(2019, 10, 26), <>Add a checklist item for <SpellLink id={SPELLS.TOTEM_MASTERY_TALENT_ELEMENTAL.id} /> uptime.</>, [Draenal]),
  change(date(2019, 10, 21), <>Add a checklist item for <SpellLink id={SPELLS.FLAME_SHOCK.id} /> uptime and early refreshes.</>, [Draenal]),
  change(date(2019, 10, 16), <>Count precast <SpellLink id={SPELLS.STORMKEEPER_TALENT.id} /> in total number of <SpellLink id={SPELLS.STORMKEEPER_TALENT.id} /> casts.</>, [TheJigglr]),
  change(date(2019, 10, 12), <>Add suggestion and checklist for Icefury efficiency.</>, [Draenal]),
  change(date(2019, 10, 12), <>Added cancelled casts to downtime checklist. Add cancelled casts suggestion.</>, [Draenal]),
  change(date(2019, 8, 21), <>Added support for <SpellLink id={SPELLS.SURGE_OF_POWER_TALENT.id} /></>, [TheJigglr]),
  change(date(2019, 8, 14), <>Updated <SpellLink id={SPELLS.LAVA_BURST.id} /> to check for <SpellLink id={SPELLS.FLAME_SHOCK.id} /> on damage instead of on cast.</>, [Draenal]),
  change(date(2019, 5, 6), <>Added support for the damage part of <SpellLink id={SPELLS.IGNEOUS_POTENTIAL.id} />.</>, [niseko]),
  change(date(2019, 3, 20), <>Fixing <SpellLink id={SPELLS.MASTER_OF_THE_ELEMENTS_TALENT.id} />-Tracker and Damage Calculation.</>, [HawkCorrigan]),
  change(date(2018, 11, 13), <>Added a basic Checklist, with the cross-spec functionalities.</>, [HawkCorrigan]),
  change(date(2018, 11, 4), <>Added support for <SpellLink id={SPELLS.PACK_SPIRIT_TRAIT.id} /> and <SpellLink id={SPELLS.SERENE_SPIRIT_TRAIT.id} /> azerite traits.</>, [niseko]),
  change(date(2018, 11, 1), <>Added support for <SpellLink id={SPELLS.ASTRAL_SHIFT.id} /> damage reduction.</>, [niseko]),
  change(date(2018, 10, 17), <>Flagged the Elemental Shaman Analyzer as supported.</>, [HawkCorrigan]),
  change(date(2018, 10, 15), <>Added Checks for the correct usage of <SpellLink id={SPELLS.STORM_ELEMENTAL_TALENT.id} /> and <SpellLink id={SPELLS.FIRE_ELEMENTAL.id} /> when talented into <SpellLink id={SPELLS.PRIMAL_ELEMENTALIST_TALENT.id} />.</>, [HawkCorrigan]),
];
<commit_msg>changelog<commit_after>import React from 'react';

import { niseko, HawkCorrigan, Draenal, TheJigglr, Zeboot } from 'CONTRIBUTORS';
import SPELLS from 'common/SPELLS';
import SpellLink from 'common/SpellLink';
import { change, date } from 'common/changelog';

export default [
  change(date(2020, 10, 18), 'Converted legacy listeners to new event filters', Zeboot),
  change(date(2020, 8, 28), <>First go at removing obsolete Spells and Azerite.</>, [HawkCorrigan]),
  change(date(2020, 3, 17), <>Adjust wording of downtime suggestion. Cancelled casts don't consume a gcd.</>, [Draenal]),
  change(date(2020, 2, 28), <>Added a statistic for <SpellLink id={SPELLS.ANCESTRAL_RESONANCE.id} />.</>, [niseko]),
  change(date(2019, 11, 29), <>Add a statistic counting the amount of maelstrom not wasted due to <SpellLink id={SPELLS.CALL_THE_THUNDER_TALENT.id} />.</>, [Draenal]),
  change(date(2019, 11, 28), <>Added a statistic for <SpellLink id={SPELLS.NATURAL_HARMONY_TRAIT.id} /> to track avg crit, haste, mastery and uptime for each</>, [Draenal]),
  change(date(2019, 11, 27), <>Add an <SpellLink id={SPELLS.EARTH_SHIELD.id} /> statistic to track uptime and healing.</>, [Draenal]),
  change(date(2019, 10, 31), <>Add a checklist item for <SpellLink id={SPELLS.ASCENDANCE_TALENT_ELEMENTAL.id} /></>, [HawkCorrigan] ),
  change(date(2019, 10, 31), <>Fixed formatting for SoP suggestion</>, [HawkCorrigan]),
  change(date(2019, 10, 26), <>Add a checklist item for <SpellLink id={SPELLS.TOTEM_MASTERY_TALENT_ELEMENTAL.id} /> uptime.</>, [Draenal]),
  change(date(2019, 10, 21), <>Add a checklist item for <SpellLink id={SPELLS.FLAME_SHOCK.id} /> uptime and early refreshes.</>, [Draenal]),
  change(date(2019, 10, 16), <>Count precast <SpellLink id={SPELLS.STORMKEEPER_TALENT.id} /> in total number of <SpellLink id={SPELLS.STORMKEEPER_TALENT.id} /> casts.</>, [TheJigglr]),
  change(date(2019, 10, 12), <>Add suggestion and checklist for Icefury efficiency.</>, [Draenal]),
  change(date(2019, 10, 12), <>Added cancelled casts to downtime checklist. Add cancelled casts suggestion.</>, [Draenal]),
  change(date(2019, 8, 21), <>Added support for <SpellLink id={SPELLS.SURGE_OF_POWER_TALENT.id} /></>, [TheJigglr]),
  change(date(2019, 8, 14), <>Updated <SpellLink id={SPELLS.LAVA_BURST.id} /> to check for <SpellLink id={SPELLS.FLAME_SHOCK.id} /> on damage instead of on cast.</>, [Draenal]),
  change(date(2019, 5, 6), <>Added support for the damage part of <SpellLink id={SPELLS.IGNEOUS_POTENTIAL.id} />.</>, [niseko]),
  change(date(2019, 3, 20), <>Fixing <SpellLink id={SPELLS.MASTER_OF_THE_ELEMENTS_TALENT.id} />-Tracker and Damage Calculation.</>, [HawkCorrigan]),
  change(date(2018, 11, 13), <>Added a basic Checklist, with the cross-spec functionalities.</>, [HawkCorrigan]),
  change(date(2018, 11, 4), <>Added support for <SpellLink id={SPELLS.PACK_SPIRIT_TRAIT.id} /> and <SpellLink id={SPELLS.SERENE_SPIRIT_TRAIT.id} /> azerite traits.</>, [niseko]),
  change(date(2018, 11, 1), <>Added support for <SpellLink id={SPELLS.ASTRAL_SHIFT.id} /> damage reduction.</>, [niseko]),
  change(date(2018, 10, 17), <>Flagged the Elemental Shaman Analyzer as supported.</>, [HawkCorrigan]),
  change(date(2018, 10, 15), <>Added Checks for the correct usage of <SpellLink id={SPELLS.STORM_ELEMENTAL_TALENT.id} /> and <SpellLink id={SPELLS.FIRE_ELEMENTAL.id} /> when talented into <SpellLink id={SPELLS.PRIMAL_ELEMENTALIST_TALENT.id} />.</>, [HawkCorrigan]),
];
<|endoftext|>


<commit_before>var chai = require("chai");
var expect = chai.expect;
var inspect = require("util").inspect;

var SuperTask = require('../supertask');
var TaskManager;
var exec = 0;
var averager = { prev: 0, current: 0 };

var ftasks = {
    t1: "var exec = 0; module.exports = function(a1, callback){ setTimeout(function(){callback(null, a1, ++exec);}, 10) };",
    t2: "module.exports = function(callback) { callback(); console.log('hey'); };"
};

function noop () { return null; }

describe('Basic Test Suite', function(){
    it('should create a new instance', function() {
        TaskManager = new SuperTask();
    });
    it('should add a local task', function(done) {
        TaskManager.addLocal('test', function(callback) {
            exec++;
            callback(null, 'hey');
        }, function(error, task) {
            if(error) throw error;
            expect(task).to.have.property('local');
            done();
        });
    });
    it('should run a task', function(done) {
        TaskManager.do('test', {}, [], function(error, result) {
            if(error) throw error;
            expect(result).to.equal('hey');
            expect(exec).to.be.gte(1);
            averager.prev = this.lastDiff;
            done();
        });
    });
    it('should calculate average execution time', function(done) {
        TaskManager.do('test', {}, [], function(error, result) {
            if(error) throw error;
            expect(result).to.equal('hey');
            expect(exec).to.be.gte(1);
            averager.current = (averager.prev + this.lastDiff)/2;
            expect(this.averageExecutionTime).to.be.equal(averager.current);
            // Sanity Check
            expect(this.averageExecutionTime).to.be.lt(1000000000);
            done();
        });
    });
    it('should deny duplicate task names', function(done) {
        TaskManager.addLocal('test', function(callback) {
            exec++;
            callback(null, 'hey');
        }, function(error) {
            expect(error.message).to.be.equal('Enable to create new task. A Task with the given name already exists.');
            done();
        });
    });
    it('should check if task exists on has call', function(done) {
       TaskManager.has('test', function(error, has) {
           if(error) throw error;
           expect(has).to.be.equal(true);
           done();
       });
    });
    it('should remove given task', function(done) {
        TaskManager.remove('test', function(error) {
            if(error) throw error;
            TaskManager.has('test', function(error, has) {
                if(error) throw error;
                expect(has).to.be.equal(false);
                done();
            });
        });
    });
});

describe('Foreign Task Suite', function(){
    it('should add a foreign task', function(done) {
        TaskManager.addForeignWithContext('foreign', ftasks.t1, {}, SuperTask.ST_MINIMAL, function(error, task) {
            if(error) throw error;
            expect(task).to.have.property('sandboxed');
            done();
        });
    });
    it('should run a foreign task', function(done) {
        TaskManager.do('foreign', {}, ['HelloWorld'], function(error, a1, count) {
            if(error) throw error;
            expect(a1).to.equal('HelloWorld');
            expect(count).to.equal(1);

            done();
        });
    });
    it('should compile and cache a foreign task', function(done) {
        TaskManager.do('foreign', {}, ['HelloWorld'], function(error, a1, count) {
            if(error) throw error;
            expect(a1).to.equal('HelloWorld');
            expect(count).to.equal(2);
            expect(this.isCompiled).to.equal(true);
            done();
        });
    });
    it('should run a foreign task with unique context', function(done) {
        TaskManager.addForeign('foreignUnique', ftasks.t2, function(error, task) {
            if(error) throw error;
            TaskManager.do('foreignUnique', { console: { log: function(m){
                expect(m).to.equal("hey");
                done();
            }}});
        });
    });
    it('should allow foreign tasks as JS functions', function(done) {
        TaskManager.addForeign('foreignFunc', function(callback) {
            console.log('hey');
            callback();
        }, function(error, task) {
            if(error) throw error;
            TaskManager.do('foreignFunc', { console: { log: function(m){
                expect(m).to.equal("hey");
                done();
            }}});
        });
    });
});

describe('Queue & Cargo Suite', function(){
    it('should queue tasks', function() {
        TaskManager.do('foreign', {}, ['HelloWorld']);
        expect(TaskManager.cargo.length()).to.be.gte(1);
    });
    it('should queue tasks in parallel', function() {
        // Queue 5 times
        var i = 0;
        for(i=0; i<5; i++) {
            TaskManager.do('foreign', {}, ['HelloWorld']);
        }
        expect(TaskManager.cargo.length()).to.be.gte(5);
    });
    it('should handle large parallel tasks', function(done) {
        this.timeout(10000);
        // Set max parallel to 5000
        TaskManager.cargo.payload = 5000;
        // Queue 5000 times
        var i = 0;
        for(i=0; i<5000; i++) {
            TaskManager.do('foreign', {}, ['HelloWorld']);
        }
        expect(TaskManager.cargo.length()).to.be.gte(5000);
        TaskManager.cargo.drain = function(){
            done();
            // Reset
            TaskManager.cargo.drain = noop;
        };
    });
    it('should respect maximum parallel execution limit', function(done) {
        TaskManager.cargo.payload = 10;
        var saturated = false;
        TaskManager.cargo.saturated = function(){
            saturated = true;
        };
        // Queue 30 times
        var i = 0;
        for(i=0; i<30; i++) {
            TaskManager.do('foreign', {}, ['HelloWorld']);
        }
        expect(saturated).to.be.equal(true);
        var c = 0;
        TaskManager.cargo.drain = function(){
            done();
            // Reset
            TaskManager.cargo.drain = noop;
        };
    });
});

describe.skip('Permission & Context Suite', function(){
    it('should contain the context with respect to permissions', function(done) {

    });
});<commit_msg>Added Restriction & Sandboxing tests<commit_after>var chai = require("chai");
var expect = chai.expect;
var inspect = require("util").inspect;

var SuperTask = require('../supertask');
var TaskManager;
var exec = 0;
var averager = { prev: 0, current: 0 };

var ftasks = {
    t1: "var exec = 0; module.exports = function(a1, callback){ setTimeout(function(){callback(null, a1, ++exec);}, 10) };",
    t2: "module.exports = function(callback) { callback(); console.log('hey'); };",
    t3: "module.exports = function(callback) { callback(null, process.hrtime()); };"
};

function noop () { return null; }

describe('Basic Test Suite', function(){
    it('should create a new instance', function() {
        TaskManager = new SuperTask();
    });
    it('should add a local task', function(done) {
        TaskManager.addLocal('test', function(callback) {
            exec++;
            callback(null, 'hey');
        }, function(error, task) {
            if(error) throw error;
            expect(task).to.have.property('local');
            done();
        });
    });
    it('should run a task', function(done) {
        TaskManager.do('test', {}, [], function(error, result) {
            if(error) throw error;
            expect(result).to.equal('hey');
            expect(exec).to.be.gte(1);
            averager.prev = this.lastDiff;
            done();
        });
    });
    it('should calculate average execution time', function(done) {
        TaskManager.do('test', {}, [], function(error, result) {
            if(error) throw error;
            expect(result).to.equal('hey');
            expect(exec).to.be.gte(1);
            averager.current = (averager.prev + this.lastDiff)/2;
            expect(this.averageExecutionTime).to.be.equal(averager.current);
            // Sanity Check
            expect(this.averageExecutionTime).to.be.lt(1000000000);
            done();
        });
    });
    it('should deny duplicate task names', function(done) {
        TaskManager.addLocal('test', function(callback) {
            exec++;
            callback(null, 'hey');
        }, function(error) {
            expect(error.message).to.be.equal('Enable to create new task. A Task with the given name already exists.');
            done();
        });
    });
    it('should check if task exists on has call', function(done) {
       TaskManager.has('test', function(error, has) {
           if(error) throw error;
           expect(has).to.be.equal(true);
           done();
       });
    });
    it('should remove given task', function(done) {
        TaskManager.remove('test', function(error) {
            if(error) throw error;
            TaskManager.has('test', function(error, has) {
                if(error) throw error;
                expect(has).to.be.equal(false);
                done();
            });
        });
    });
});

describe('Foreign Task Suite', function(){
    it('should add a foreign task', function(done) {
        TaskManager.addForeignWithContext('foreign', ftasks.t1, {}, SuperTask.ST_MINIMAL, function(error, task) {
            if(error) throw error;
            expect(task).to.have.property('sandboxed');
            done();
        });
    });
    it('should run a foreign task', function(done) {
        TaskManager.do('foreign', {}, ['HelloWorld'], function(error, a1, count) {
            if(error) throw error;
            expect(a1).to.equal('HelloWorld');
            expect(count).to.equal(1);

            done();
        });
    });
    it('should compile and cache a foreign task', function(done) {
        TaskManager.do('foreign', {}, ['HelloWorld'], function(error, a1, count) {
            if(error) throw error;
            expect(a1).to.equal('HelloWorld');
            expect(count).to.equal(2);
            expect(this.isCompiled).to.equal(true);
            done();
        });
    });
    it('should run a foreign task with unique context', function(done) {
        TaskManager.addForeign('foreignUnique', ftasks.t2, function(error, task) {
            if(error) throw error;
            TaskManager.do('foreignUnique', { console: { log: function(m){
                expect(m).to.equal("hey");
                done();
            }}});
        });
    });
    it('should allow foreign tasks as JS functions', function(done) {
        TaskManager.addForeign('foreignFunc', function(callback) {
            console.log('hey');
            callback();
        }, function(error, task) {
            if(error) throw error;
            TaskManager.do('foreignFunc', { console: { log: function(m){
                expect(m).to.equal("hey");
                done();
            }}});
        });
    });
});

describe('Queue & Cargo Suite', function(){
    it('should queue tasks', function() {
        TaskManager.do('foreign', {}, ['HelloWorld']);
        expect(TaskManager.cargo.length()).to.be.gte(1);
    });
    it('should queue tasks in parallel', function() {
        // Queue 5 times
        var i = 0;
        for(i=0; i<5; i++) {
            TaskManager.do('foreign', {}, ['HelloWorld']);
        }
        expect(TaskManager.cargo.length()).to.be.gte(5);
    });
    it('should handle large parallel tasks', function(done) {
        this.timeout(10000);
        // Set max parallel to 5000
        TaskManager.cargo.payload = 5000;
        // Queue 5000 times
        var i = 0;
        for(i=0; i<5000; i++) {
            TaskManager.do('foreign', {}, ['HelloWorld']);
        }
        expect(TaskManager.cargo.length()).to.be.gte(5000);
        TaskManager.cargo.drain = function(){
            done();
            // Reset
            TaskManager.cargo.drain = noop;
        };
    });
    it('should respect maximum parallel execution limit', function(done) {
        TaskManager.cargo.payload = 10;
        var saturated = false;
        TaskManager.cargo.saturated = function(){
            saturated = true;
        };
        // Queue 30 times
        var i = 0;
        for(i=0; i<30; i++) {
            TaskManager.do('foreign', {}, ['HelloWorld']);
        }
        expect(saturated).to.be.equal(true);
        var c = 0;
        TaskManager.cargo.drain = function(){
            done();
            // Reset
            TaskManager.cargo.drain = noop;
        };
    });
});

describe('Permission & Context Suite', function(){
    /* Add More Tests */
    it('should contain the context with respect to permissions', function(done) {
        TaskManager.addForeignWithContext('foreignT3', ftasks.t3, {}, SuperTask.ST_UNRESTRICTED, function(error, task) {
            if(error) throw error;
            expect(task).to.have.property('sandboxed');
            TaskManager.do('foreignT3', {}, [], function(error, time) {
                expect(time).to.be.an('array');
                expect(time).to.have.length(2);
                done();
            });
        });
    });
    it('should restrict the context', function(done) {
        TaskManager.addForeignWithContext('foreignT3C', ftasks.t3, {}, SuperTask.ST_RESTRICTED, function(error, task) {
            if(error) throw error;
            expect(task).to.have.property('sandboxed');
            TaskManager.do('foreignT3C', {}, [], function(error, time) {
                expect(error.message).to.have.string('process is not defined');
                done();
            });
        });
    });
});<|endoftext|>


<commit_before>'use strict';

describe('ngTouch', function () {

	var element;

	beforeEach(function () {
		module('ngTouch');
	});

	it('should get called on a touchstart', inject(function($rootScope, $compile) {
		element = $compile('

')($rootScope);
		$rootScope.$digest();
		expect($rootScope.started).toBeUndefined();
		element.triggerHandler('touchstart');
		expect($rootScope.started).toEqual(true);
	}));

	it('should get called on touchmove', inject(function($rootScope, $compile) {
		element = $compile('

')($rootScope);
		$rootScope.$digest();
		expect($rootScope.moved).toBeUndefined();
		element.triggerHandler('touchstart');
		element.triggerHandler('touchmove');
		element.triggerHandler('touchend');
		expect($rootScope.moved).toEqual(true);
	}));

	it('should get called on touchend', inject(function($rootScope, $compile) {
		element =  $compile('

')($rootScope);
		$rootScope.$digest();
		expect($rootScope.ended).toBeUndefined();
		element.triggerHandler('touchstart');
		element.triggerHandler('touchend');
		expect($rootScope.ended).toEqual(true);
	}));
});

// var myApp = angular.module('myApp',[]);

// myApp.controller('HelloWorldController', ['$scope', function($scope) {
// 	$scope.greeting = 'Hello World!';
// }]);

// describe('Hello World example', function() {

// 	beforeEach(module('myApp'));

// 	var HelloWorldController,
// 	scope;

// 	beforeEach(inject(function ($rootScope, $controller) {
// 		scope = $rootScope.$new();
// 		HelloWorldController = $controller('HelloWorldController', {
// 			$scope: scope
// 		});
// 	}));
// 	it('says hello world!', function () {
// 		expect(scope.greeting).toEqual("Hello World!");
// 	});

// });
<commit_msg>Update spec.js<commit_after>'use strict';

describe('ngTouch', function () {

	var element;

	beforeEach(function () {
		module('ngTouch');
	});

	it('should get called on touchstart', inject(function($rootScope, $compile) {
		element = $compile('

')($rootScope);
		$rootScope.$digest();
		expect($rootScope.started).toBeUndefined();
		element.triggerHandler('touchstart');
		expect($rootScope.started).toEqual(true);
	}));

	it('should get called on touchmove', inject(function($rootScope, $compile) {
		element = $compile('

')($rootScope);
		$rootScope.$digest();
		expect($rootScope.moved).toBeUndefined();
		element.triggerHandler('touchstart');
		element.triggerHandler('touchmove');
		element.triggerHandler('touchend');
		expect($rootScope.moved).toEqual(true);
	}));

	it('should get called on touchend', inject(function($rootScope, $compile) {
		element =  $compile('

')($rootScope);
		$rootScope.$digest();
		expect($rootScope.ended).toBeUndefined();
		element.triggerHandler('touchstart');
		element.triggerHandler('touchend');
		expect($rootScope.ended).toEqual(true);
	}));
});

// var myApp = angular.module('myApp',[]);

// myApp.controller('HelloWorldController', ['$scope', function($scope) {
// 	$scope.greeting = 'Hello World!';
// }]);

// describe('Hello World example', function() {

// 	beforeEach(module('myApp'));

// 	var HelloWorldController,
// 	scope;

// 	beforeEach(inject(function ($rootScope, $controller) {
// 		scope = $rootScope.$new();
// 		HelloWorldController = $controller('HelloWorldController', {
// 			$scope: scope
// 		});
// 	}));
// 	it('says hello world!', function () {
// 		expect(scope.greeting).toEqual("Hello World!");
// 	});

// });
<|endoftext|>