<commit_before>/*global window:true,describe:true, it:true, expect: true, Backbone: true, beforeEach: true, chai:true, _:true*/
/*jshint expr:true */

describe("ViewMaster", function(){

  var Puppet = Backbone.ViewMaster.extend({
    name: "puppet",

    constructor: function(opts) {
      Backbone.ViewMaster.prototype.constructor.apply(this, arguments);
      if (opts && opts.name) {
        this.name = opts.name;
      }
    },

    template: function(){
      return "
" + this.name + "
";
    }
  });

  var Master = Backbone.ViewMaster.extend({
    template: function(){
      return '

';
    }
  });

  it("is installed to Backbone namespace", function(){
    expect(Backbone.ViewMaster).to.be.ok;
  });

  it("Adds model to instance", function(){
    var model = new Backbone.Model();
    var view = new Backbone.ViewMaster({
      model: model
    });
    expect(view.model === model).to.be.ok;
  });

  it("renders child view from setViews", function(){
    var m = new Master();
    m.setViews(".container", new Puppet());
    m.render();
    expect(m.$el).to.have.text("puppet");
  });

  it("renders child view from setViews using an array", function(){
    var m = new Master();
    m.setViews(".container", [new Puppet()]);
    m.render();
    expect(m.$el).to.have.text("puppet");
  });

  it("does not render child views twice", function(){
    var m = new Master();
    var p = new Puppet();
    var spy = p.render = chai.spy(p.render);

    m.setViews(".container", p);
    m.render();
    m.render();
    expect(spy).to.have.been.called.once;
  });

  it("only new child views are rendered on ViewMaster#render()", function(){
    var m = new Master();
    var first = new Puppet({ name: "first" });
    first.render = chai.spy(first.render);

    m.setViews(".container", first);
    m.render();

    var second = new Puppet({ name: "second" });
    second.render = chai.spy(second.render);
    m.appendViews(".container", second);
    m.renderViews();

    expect(first.render).to.have.been.called.once;
    expect(second.render).to.have.been.called.once;

    expect(m.$el).to.have(first.$el);
    expect(m.$el).to.have(second.$el);
  });

  it("force option renders child views", function(){
    var m = new Master();
    var p = new Puppet();
    var spy = p.render = chai.spy(p.render);

    m.setViews(".container", p);
    m.render();
    m.render({ force: true });
    expect(spy).to.have.been.called.twice;
  });

  it("can add new views with appendViews() after initial render", function(){
    var m = new Master();
    m.setViews(".container", new Puppet());
    m.render();

    m.appendViews(".container", new Puppet({ name: "newview" }));
    m.render();
    var childs = m.$(".child");

    expect(childs[0].innerHTML).to.eq("puppet");
    expect(childs[1].innerHTML).to.eq("newview");
  });

  it("can add new views with prependViews() after initial render", function(){
    var m = new Master();
    m.setViews(".container", new Puppet());
    m.render();

    m.prependViews(".container", new Puppet({ name: "newview" }));
    m.renderViews();
    var childs = m.$(".child");
    expect(childs[1].innerHTML).to.eq("puppet");
    expect(childs[0].innerHTML).to.eq("newview");
  });

  it("updates view order", function(){
    var m = new Master();
    var first = new Puppet({ name: "first" });
    var second = new Puppet({ name: "second" });

    m.setViews(".container", [first, second]);
    m.render();

    m.setViews(".container", [second, first]);
    m.renderViews();

    var childs = m.$(".child");
    expect(childs[0].innerHTML).to.eq("second");
    expect(childs[1].innerHTML).to.eq("first");
  });

  it("setViews&renderViews() removes old views", function(){
    var m = new Master();
    var views = [
      new Puppet({ name: "first" }),
      new Puppet({ name: "second" })
    ];

    m.setViews(".container", views);
    m.render();

    m.setViews(".container", views[1]);
    m.renderViews();

    expect(m.$el).to.not.have(views[0].$el);
    expect(m.$el).to.have(views[1].$el);

  });

  it("does not render already rendered views", function(){
    var master = new Master();
    var child = new Puppet();
    child.render = chai.spy(child.render);
    child.render();

    master.setViews(".container", child);
    master.render();

    expect(child.render).to.have.been.called.once;
    expect(master.$el).to.have(child.$el);
  });

  it("removeViews() removes view", function(){
    var m = new Master();
    var views = [
      new Puppet({ name: "first" }),
      new Puppet({ name: "second" })
    ];

    m.setViews(".container", views);
    m.render();

    m.removeViews(".container", views[0]);

    m.renderViews();

    expect(m.$el).to.not.have(views[0].$el);
    expect(m.$el).to.have(views[1].$el);

  });

  it("child.remove() removes from parent", function(){
    var parent = new Master();
    var child = new Puppet({ name: "first" });

    parent.setViews(".container", child);
    parent.render();

    child.remove();
    parent.render();
    expect(parent.$el).to.not.have(child.$el);
    expect(_.contains(parent.getViews(".container"), child)).to.not.be.ok;
  });

  describe("with deeply nested views", function(){

    var ViewList = Backbone.ViewMaster.extend({
      template: function() {
        return '
List

';
      }
    });

    var Layout = Backbone.ViewMaster.extend({

      template: function() {
        return '
Title

';
      }

    });

    it("renders them all", function(){
      var children = [
        new Puppet({ name: "first" }),
        new Puppet({ name: "second" })
      ];
      var list = new ViewList();
      list.setViews(".items", children);
      var layout = new Layout();
      layout.setViews(".container", list);

      layout.render();

      expect(layout.$el).to.contain("List");
      expect(layout.$el).to.contain("first");
      expect(layout.$el).to.contain("second");

    });

    it("does not render nested views twice", function(){

      var children = [
        new Puppet({ name: "first" }),
        new Puppet({ name: "second" })
      ];

      var spy = children[1].render = chai.spy(children[1].render);

      var list = new ViewList();
      list.setViews(".items", children);

      var layout = new Layout();
      layout.setViews(".container", list);

      layout.render();
      layout.render();

      expect(spy).to.have.been.called.once;

    });

    it("option force calls nested views twice", function(){

      var children = [
        new Puppet({ name: "first" }),
        new Puppet({ name: "second" })
      ];

      var spy = children[1].render = chai.spy(children[1].render);

      var list = new ViewList();
      list.setViews(".items", children);

      var layout = new Layout();
      layout.setViews(".container", list);

      layout.render();
      layout.render({ force: true });

      expect(spy).to.have.been.called.twice;

    });

    it("can move child to other parent", function(){
      var Parent = Backbone.ViewMaster.extend({
        template: function() {
          return "

";
        }
      });

      var parent1 = new Parent();
      var parent2 = new Parent();
      var child = new Backbone.ViewMaster();
      child.template = function() {
        return "child";
      };

      parent1.setViews(".container", child);
      parent1.render();

      parent2.setViews(".container", child);

      parent2.render();
      parent1.render();

      expect(parent2.$el).to.have(child.$el);
      expect(parent1.$el).to.not.have(child.$el);

    });

  });

  describe("CoffeeScript", function() {
    it("works with CoffeeScript classes", function(done){
      var model = new Backbone.Model();
      model.on("done", done);

      var view = new window.CoffeeView({
        model: model
      });

      model.trigger("test");

    });
  });
});
<commit_msg>Assert child in parent<commit_after>/*global window:true,describe:true, it:true, expect: true, Backbone: true, beforeEach: true, chai:true, _:true*/
/*jshint expr:true */

describe("ViewMaster", function(){

  var Puppet = Backbone.ViewMaster.extend({
    name: "puppet",

    constructor: function(opts) {
      Backbone.ViewMaster.prototype.constructor.apply(this, arguments);
      if (opts && opts.name) {
        this.name = opts.name;
      }
    },

    template: function(){
      return "
" + this.name + "
";
    }
  });

  var Master = Backbone.ViewMaster.extend({
    template: function(){
      return '

';
    }
  });

  it("is installed to Backbone namespace", function(){
    expect(Backbone.ViewMaster).to.be.ok;
  });

  it("Adds model to instance", function(){
    var model = new Backbone.Model();
    var view = new Backbone.ViewMaster({
      model: model
    });
    expect(view.model === model).to.be.ok;
  });

  it("renders child view from setViews", function(){
    var m = new Master();
    m.setViews(".container", new Puppet());
    m.render();
    expect(m.$el).to.have.text("puppet");
  });

  it("renders child view from setViews using an array", function(){
    var m = new Master();
    m.setViews(".container", [new Puppet()]);
    m.render();
    expect(m.$el).to.have.text("puppet");
  });

  it("does not render child views twice", function(){
    var m = new Master();
    var p = new Puppet();
    var spy = p.render = chai.spy(p.render);

    m.setViews(".container", p);
    m.render();
    m.render();
    expect(spy).to.have.been.called.once;
  });

  it("only new child views are rendered on ViewMaster#render()", function(){
    var m = new Master();
    var first = new Puppet({ name: "first" });
    first.render = chai.spy(first.render);

    m.setViews(".container", first);
    m.render();

    var second = new Puppet({ name: "second" });
    second.render = chai.spy(second.render);
    m.appendViews(".container", second);
    m.renderViews();

    expect(first.render).to.have.been.called.once;
    expect(second.render).to.have.been.called.once;

    expect(m.$el).to.have(first.$el);
    expect(m.$el).to.have(second.$el);
  });

  it("force option renders child views", function(){
    var m = new Master();
    var p = new Puppet();
    var spy = p.render = chai.spy(p.render);

    m.setViews(".container", p);
    m.render();
    m.render({ force: true });
    expect(spy).to.have.been.called.twice;
  });

  it("can add new views with appendViews() after initial render", function(){
    var m = new Master();
    m.setViews(".container", new Puppet());
    m.render();

    m.appendViews(".container", new Puppet({ name: "newview" }));
    m.render();
    var childs = m.$(".child");

    expect(childs[0].innerHTML).to.eq("puppet");
    expect(childs[1].innerHTML).to.eq("newview");
  });

  it("can add new views with prependViews() after initial render", function(){
    var m = new Master();
    m.setViews(".container", new Puppet());
    m.render();

    m.prependViews(".container", new Puppet({ name: "newview" }));
    m.renderViews();
    var childs = m.$(".child");
    expect(childs[1].innerHTML).to.eq("puppet");
    expect(childs[0].innerHTML).to.eq("newview");
  });

  it("updates view order", function(){
    var m = new Master();
    var first = new Puppet({ name: "first" });
    var second = new Puppet({ name: "second" });

    m.setViews(".container", [first, second]);
    m.render();

    m.setViews(".container", [second, first]);
    m.renderViews();

    var childs = m.$(".child");
    expect(childs[0].innerHTML).to.eq("second");
    expect(childs[1].innerHTML).to.eq("first");
  });

  it("setViews&renderViews() removes old views", function(){
    var m = new Master();
    var views = [
      new Puppet({ name: "first" }),
      new Puppet({ name: "second" })
    ];

    m.setViews(".container", views);
    m.render();

    m.setViews(".container", views[1]);
    m.renderViews();

    expect(m.$el).to.not.have(views[0].$el);
    expect(m.$el).to.have(views[1].$el);

  });

  it("does not render already rendered views", function(){
    var master = new Master();
    var child = new Puppet();
    child.render = chai.spy(child.render);
    child.render();

    master.setViews(".container", child);
    master.render();

    expect(child.render).to.have.been.called.once;
    expect(master.$el).to.have(child.$el);
  });

  it("removeViews() removes view", function(){
    var m = new Master();
    var views = [
      new Puppet({ name: "first" }),
      new Puppet({ name: "second" })
    ];

    m.setViews(".container", views);
    m.render();

    m.removeViews(".container", views[0]);

    m.renderViews();

    expect(m.$el).to.not.have(views[0].$el);
    expect(m.$el).to.have(views[1].$el);

  });

  it("child.remove() removes from parent", function(){
    var parent = new Master();
    var child = new Puppet({ name: "first" });

    parent.setViews(".container", child);
    parent.render();
    expect(_.contains(parent.getViews(".container"), child)).to.be.ok;

    child.remove();
    parent.render();
    expect(parent.$el).to.not.have(child.$el);
    expect(_.contains(parent.getViews(".container"), child)).to.not.be.ok;
  });

  describe("with deeply nested views", function(){

    var ViewList = Backbone.ViewMaster.extend({
      template: function() {
        return '
List

';
      }
    });

    var Layout = Backbone.ViewMaster.extend({

      template: function() {
        return '
Title

';
      }

    });

    it("renders them all", function(){
      var children = [
        new Puppet({ name: "first" }),
        new Puppet({ name: "second" })
      ];
      var list = new ViewList();
      list.setViews(".items", children);
      var layout = new Layout();
      layout.setViews(".container", list);

      layout.render();

      expect(layout.$el).to.contain("List");
      expect(layout.$el).to.contain("first");
      expect(layout.$el).to.contain("second");

    });

    it("does not render nested views twice", function(){

      var children = [
        new Puppet({ name: "first" }),
        new Puppet({ name: "second" })
      ];

      var spy = children[1].render = chai.spy(children[1].render);

      var list = new ViewList();
      list.setViews(".items", children);

      var layout = new Layout();
      layout.setViews(".container", list);

      layout.render();
      layout.render();

      expect(spy).to.have.been.called.once;

    });

    it("option force calls nested views twice", function(){

      var children = [
        new Puppet({ name: "first" }),
        new Puppet({ name: "second" })
      ];

      var spy = children[1].render = chai.spy(children[1].render);

      var list = new ViewList();
      list.setViews(".items", children);

      var layout = new Layout();
      layout.setViews(".container", list);

      layout.render();
      layout.render({ force: true });

      expect(spy).to.have.been.called.twice;

    });

    it("can move child to other parent", function(){
      var Parent = Backbone.ViewMaster.extend({
        template: function() {
          return "

";
        }
      });

      var parent1 = new Parent();
      var parent2 = new Parent();
      var child = new Backbone.ViewMaster();
      child.template = function() {
        return "child";
      };

      parent1.setViews(".container", child);
      parent1.render();

      parent2.setViews(".container", child);

      parent2.render();
      parent1.render();

      expect(parent2.$el).to.have(child.$el);
      expect(parent1.$el).to.not.have(child.$el);

    });

  });

  describe("CoffeeScript", function() {
    it("works with CoffeeScript classes", function(done){
      var model = new Backbone.Model();
      model.on("done", done);

      var view = new window.CoffeeView({
        model: model
      });

      model.trigger("test");

    });
  });
});
<|endoftext|>


<commit_before>/* Python 'str' type */

var str = __inherit(object);

str.__name__ = 'str';
str.prototype.__class__ = str;

str.prototype.__init__ = function(s) {
    if (!defined(s)) {
        this._obj = '';
    } else {
        if (typeof(s) === "string") {
            this._obj = s;
        } else if (defined(s.toString)) {
            this._obj = s.toString();
        } else if (defined(s.__str__)) {
            this._obj = js(s.__str__());
        } else
            this._obj = js(s);
    }
};

str.prototype.__str__ = function () {
    return this;
};

str.prototype.__eq__ = function (other) {
    if (other.__class__ == this.__class__) {
        if (len(this) != len(other))
            return false;
        for (var i = 0; i < len(this); i++) {
            if (this._obj[i] != other._obj[i])
                return false;
        }
        return true;
    } else
        return false;
};

str.prototype.toString = function () {
    return js(this.__str__());
};

str.prototype._js_ = function () {
    return this._obj;
};

str.prototype.__hash__ = function () {
    var value = 0x345678;
    var length = this.__len__();

    for (var index in this._obj) {
        value = ((1000003*value) & 0xFFFFFFFF) ^ hash(this._obj[index]);
        value = value ^ length;
    }

    if (value == -1) {
        value = -2;
    }

    return value;
};

str.prototype.__len__ = function() {
    return this._obj.length;
};

str.prototype.__iter__ = function() {
    return iter.__call__(this._obj);
};

str.prototype.__bool__ = function() {
    return py_builtins.bool(this._obj);
};

str.prototype.__eq__ = function(s) {
    if (typeof(s) === "string")
        return this._obj == s;
    else if (isinstance(s, str))
        return this._obj == s._obj;
    else
        return false;
};

str.prototype.__contains__ = function(item) {
    for (var index in this._obj) {
        if (item == this._obj[index]) {
            return true;
        }
    }

    return false;
};

str.prototype.__getitem__ = function(index) {

    var seq;
    if (isinstance(index, _slice)) {
        var s = index;
        var inds = s.indices(len(this));
        var start = inds.__getitem__(0);
        var stop = inds.__getitem__(1);
        var step = inds.__getitem__(2);
        seq = "";
        for (var i = start; i < stop; i += step) {
            seq = seq + js(this.__getitem__(i));
        }
        return this.__class__.__call__(seq);
    } else if ((index >= 0) && (index < len(this)))
        return this._obj[index];
    else if ((index < 0) && (index >= -len(this)))
        return this._obj[index+len(this)];
    else
        throw new py_builtins.IndexError("string index out of range");
};

str.prototype.__setitem__ = function(index, value) {
    throw new py_builtins.TypeError("'str' object doesn't support item assignment");
};

str.prototype.__delitem__ = function(index) {
    throw new py_builtins.TypeError("'str' object doesn't support item deletion");
};

str.prototype.count = function(str, start, end) {
    if (!defined(start))
        start = 0;
    if (!defined(end))
        end = null;
    var count = 0;
    s = this.__getitem__(slice(start, end));
    idx = s.find(str);
    while (idx != -1) {
        count += 1;
        s = s.__getitem__(slice(idx+1, null));
        idx = s.find(str);
    }
    return count;
};

str.prototype.index = function(value, start, end) {
    if (!defined(start)) {
        start = 0;
    }

    for (var i = start; !defined(end) || (start < end); i++) {
        var _value = this._obj[i];

        if (!defined(_value)) {
            break;
        }

        if (_value == value) {
            return i;
        }
    }

    throw new py_builtins.ValueError("substring not found");
};

str.prototype.find = function(s) {
    return this._obj.search(s);
};

str.prototype.rfind = function(s) {
    rev = function(s) {
        var a = list(str.__call__(s));
        a.reverse();
        a = str.__call__("").join(a);
        return a;
    }
    var a = rev(this);
    var b = rev(s);
    var r = a.find(b);
    if (r == -1)
        return r;
    return len(this)-len(b)-r
};

str.prototype.join = function(s) {
    return str.__call__(js(s).join(js(this)));
};

str.prototype.replace = function(old, _new, count) {
    old = js(old);
    _new = js(_new);
    var old_s;
    var new_s;

    if (defined(count))
        count = js(count);
    else
        count = -1;
    old_s = "";
    new_s = this._obj;
    while ((count != 0) && (new_s != old_s)) {
        old_s = new_s;
        new_s = new_s.replace(old, _new);
        count -= 1;
    }
    return str.__call__(new_s);
};

str.prototype.lstrip = function(chars) {
    if (len(this) == 0)
        return this;
    if (defined(chars))
        chars = tuple(chars);
    else
        chars = tuple(["\n", "\t", " "]);
    var i = 0;
    while ((i < len(this)) && (chars.__contains__(this.__getitem__(i)))) {
        i += 1;
    }
    return this.__getitem__(slice(i, null));
};

str.prototype.rstrip = function(chars) {
    if (len(this) == 0)
        return this
    if (defined(chars))
        chars = tuple(chars);
    else
        chars = tuple(["\n", "\t", " "]);
    var i = len(this)-1;
    while ((i >= 0) && (chars.__contains__(this.__getitem__(i)))) {
        i -= 1;
    }
    return this.__getitem__(slice(i+1));
};

str.prototype.strip = function(chars) {
    return this.lstrip(chars).rstrip(chars);
};

str.prototype.split = function(sep) {
    if (defined(sep)) {
        var r = list(this._obj.split(sep));
        var r_new = list([]);
        iterate(iter.__call__(r), function(item) {
                r_new.append(str.__call__(item));
        });
        return r_new;
    }
    else {
        var r_new = list([]);
        iterate(iter.__call__(this.split(" ")), function(item) {
                if (len(item) > 0)
                    r_new.append(item);
        });
        return r_new;
    }
};

str.prototype.splitlines = function() {
    return this.split("\n");
};

str.prototype.lower = function() {
    return str.__call__(this._obj.toLowerCase());
};

str.prototype.upper = function() {
    return str.__call__(this._obj.toUpperCase());
};

<commit_msg> * Updated 25-type-str.js to use list.__call__() instead of list()<commit_after>/* Python 'str' type */

var str = __inherit(object);

str.__name__ = 'str';
str.prototype.__class__ = str;
str.prototype.MARK = "str";

str.prototype.__init__ = function(s) {
    if (!defined(s)) {
        this._obj = '';
    } else {
        if (typeof(s) === "string") {
            this._obj = s;
        } else if (defined(s.toString)) {
            this._obj = s.toString();
        } else if (defined(s.__str__)) {
            this._obj = js(s.__str__());
        } else
            this._obj = js(s);
    }
};

str.prototype.__str__ = function () {
    return this;
};

str.prototype.__eq__ = function (other) {
    if (other.__class__ == this.__class__) {
        if (len(this) != len(other))
            return false;
        for (var i = 0; i < len(this); i++) {
            if (this._obj[i] != other._obj[i])
                return false;
        }
        return true;
    } else
        return false;
};

str.prototype.toString = function () {
    return js(this.__str__());
};

str.prototype._js_ = function () {
    return this._obj;
};

str.prototype.__hash__ = function () {
    var value = 0x345678;
    var length = this.__len__();

    for (var index in this._obj) {
        value = ((1000003*value) & 0xFFFFFFFF) ^ hash(this._obj[index]);
        value = value ^ length;
    }

    if (value == -1) {
        value = -2;
    }

    return value;
};

str.prototype.__len__ = function() {
    return this._obj.length;
};

str.prototype.__iter__ = function() {
    return iter.__call__(this._obj);
};

str.prototype.__bool__ = function() {
    return py_builtins.bool(this._obj);
};

str.prototype.__eq__ = function(s) {
    if (typeof(s) === "string")
        return this._obj == s;
    else if (isinstance(s, str))
        return this._obj == s._obj;
    else
        return false;
};

str.prototype.__contains__ = function(item) {
    for (var index in this._obj) {
        if (item == this._obj[index]) {
            return true;
        }
    }

    return false;
};

str.prototype.__getitem__ = function(index) {

    var seq;
    if (isinstance(index, _slice)) {
        var s = index;
        var inds = s.indices(len(this));
        var start = inds.__getitem__(0);
        var stop = inds.__getitem__(1);
        var step = inds.__getitem__(2);
        seq = "";
        for (var i = start; i < stop; i += step) {
            seq = seq + js(this.__getitem__(i));
        }
        return this.__class__.__call__(seq);
    } else if ((index >= 0) && (index < len(this)))
        return this._obj[index];
    else if ((index < 0) && (index >= -len(this)))
        return this._obj[index+len(this)];
    else
        throw new py_builtins.IndexError("string index out of range");
};

str.prototype.__setitem__ = function(index, value) {
    throw new py_builtins.TypeError("'str' object doesn't support item assignment");
};

str.prototype.__delitem__ = function(index) {
    throw new py_builtins.TypeError("'str' object doesn't support item deletion");
};

str.prototype.count = function(str, start, end) {
    if (!defined(start))
        start = 0;
    if (!defined(end))
        end = null;
    var count = 0;
    s = this.__getitem__(slice(start, end));
    idx = s.find(str);
    while (idx != -1) {
        count += 1;
        s = s.__getitem__(slice(idx+1, null));
        idx = s.find(str);
    }
    return count;
};

str.prototype.index = function(value, start, end) {
    if (!defined(start)) {
        start = 0;
    }

    for (var i = start; !defined(end) || (start < end); i++) {
        var _value = this._obj[i];

        if (!defined(_value)) {
            break;
        }

        if (_value == value) {
            return i;
        }
    }

    throw new py_builtins.ValueError("substring not found");
};

str.prototype.find = function(s) {
    return this._obj.search(s);
};

str.prototype.rfind = function(s) {
    rev = function(s) {
        var a = list.__call__(str.__call__(s));
        a.reverse();
        a = str.__call__("").join(a);
        return a;
    }
    var a = rev(this);
    var b = rev(s);
    var r = a.find(b);
    if (r == -1)
        return r;
    return len(this)-len(b)-r
};

str.prototype.join = function(s) {
    return str.__call__(js(s).join(js(this)));
};

str.prototype.replace = function(old, _new, count) {
    old = js(old);
    _new = js(_new);
    var old_s;
    var new_s;

    if (defined(count))
        count = js(count);
    else
        count = -1;
    old_s = "";
    new_s = this._obj;
    while ((count != 0) && (new_s != old_s)) {
        old_s = new_s;
        new_s = new_s.replace(old, _new);
        count -= 1;
    }
    return str.__call__(new_s);
};

str.prototype.lstrip = function(chars) {
    if (len(this) == 0)
        return this;
    if (defined(chars))
        chars = tuple(chars);
    else
        chars = tuple(["\n", "\t", " "]);
    var i = 0;
    while ((i < len(this)) && (chars.__contains__(this.__getitem__(i)))) {
        i += 1;
    }
    return this.__getitem__(slice(i, null));
};

str.prototype.rstrip = function(chars) {
    if (len(this) == 0)
        return this
    if (defined(chars))
        chars = tuple(chars);
    else
        chars = tuple(["\n", "\t", " "]);
    var i = len(this)-1;
    while ((i >= 0) && (chars.__contains__(this.__getitem__(i)))) {
        i -= 1;
    }
    return this.__getitem__(slice(i+1));
};

str.prototype.strip = function(chars) {
    return this.lstrip(chars).rstrip(chars);
};

str.prototype.split = function(sep) {
    if (defined(sep)) {
        var r = list.__call__(this._obj.split(sep));
        var r_new = list.__call__([]);
        iterate(iter.__call__(r), function(item) {
                r_new.append(str.__call__(item));
        });
        return r_new;
    }
    else {
        var r_new = list.__call__([]);
        iterate(iter.__call__(this.split(" ")), function(item) {
                if (len(item) > 0)
                    r_new.append(item);
        });
        return r_new;
    }
};

str.prototype.splitlines = function() {
    return this.split("\n");
};

str.prototype.lower = function() {
    return str.__call__(this._obj.toLowerCase());
};

str.prototype.upper = function() {
    return str.__call__(this._obj.toUpperCase());
};

<|endoftext|>


<commit_before>
// 3D code partially grabbed from http://dev.opera.com/articles/view/porting-3d-graphics-to-the-web-webgl-intro-part-2/

document.addEventListener('DOMContentLoaded', function() {
    if ( ! Detector.webgl ) Detector.addGetWebGLMessage();

    var SCREEN_WIDTH = window.innerWidth;
    var SCREEN_HEIGHT = window.innerHeight;
    var FLOOR = 0;

    var container;

    var camera, scene;
    var webglRenderer;

    var zmesh, geometry;

    var mouseX = 0, mouseY = 0;

    var windowHalfX = window.innerWidth / 2;
    var windowHalfY = window.innerHeight / 2;

    document.addEventListener( 'mousemove', onDocumentMouseMove, false );
    document.addEventListener( 'mousewheel', onDocumentMouseWheel, false );

    init();
    animate();

    function init() {

      var closeEl=document.querySelector(".close");
      if (closeEl) {
        closeEl.addEventListener('click', function() {
          window.close();
        });
      };

      container = document.createElement( 'div' );
      document.body.appendChild( container );

      // camera
      camera = new THREE.PerspectiveCamera( 75, SCREEN_WIDTH / SCREEN_HEIGHT, 1, 100000 );
      camera.position.z = 75;

      //scene
      scene = new THREE.Scene();

      // lights
      var ambient = new THREE.AmbientLight( 0xffffff );
      scene.add( ambient );

      // more lights
      var directionalLight = new THREE.DirectionalLight( 0xffeedd );
      directionalLight.position.set( 0, -70, 100 ).normalize();
      scene.add( directionalLight );

      // renderer
      webglRenderer = new THREE.WebGLRenderer();
      webglRenderer.setSize( SCREEN_WIDTH, SCREEN_HEIGHT );
      webglRenderer.domElement.style.position = "relative";
      container.appendChild( webglRenderer.domElement );

      // load ascii model
      var jsonLoader = new THREE.JSONLoader();
      jsonLoader.load( "obj/html5rocks.js", function( geometry ) { createScene( geometry ) } );

    }

    function createScene( geometry ) {
      zmesh = new THREE.Mesh( geometry, new THREE.MeshFaceMaterial() );
      zmesh.position.set( -10, -10, 0 );
      zmesh.scale.set( 1, 1, 1 );
      scene.add( zmesh );
    }

    function onDocumentMouseWheel(event) {
      camera.position.z -= event.wheelDelta/120*3;
    }

    function onDocumentMouseMove(event) {
      mouseX = ( event.clientX - windowHalfX );
      mouseY = ( event.clientY - windowHalfY );
    }

    function animate() {
      requestAnimationFrame( animate );
      render();
    }

    function render() {

      if (zmesh) {
	zmesh.rotation.set(-mouseY/250 + 1, -mouseX/100, 0);
      }
      webglRenderer.render( scene, camera );
    }

});

<commit_msg>WebGL sample: Attempt to use pointer lock when mouse is down<commit_after>
// 3D code partially grabbed from http://dev.opera.com/articles/view/porting-3d-graphics-to-the-web-webgl-intro-part-2/

document.addEventListener('DOMContentLoaded', function() {
    if ( ! Detector.webgl ) Detector.addGetWebGLMessage();

    var SCREEN_WIDTH = window.innerWidth;
    var SCREEN_HEIGHT = window.innerHeight;
    var FLOOR = 0;

    var container;

    var camera, scene;
    var webglRenderer;

    var zmesh, geometry;

    var mouseX = 0, mouseY = 0;
    var mousemoveX = 0, mousemoveY = 0;

    var windowHalfX = window.innerWidth / 2;
    var windowHalfY = window.innerHeight / 2;

    document.addEventListener( 'mousedown', onDocumentMouseDown, false );
    document.addEventListener( 'mouseup', onDocumentMouseUp, false );
    document.addEventListener( 'mousemove', onDocumentMouseMove, false );
    document.addEventListener( 'mousewheel', onDocumentMouseWheel, false );

    init();
    animate();

    function init() {

      var closeEl=document.querySelector(".close");
      if (closeEl) {
        closeEl.addEventListener('click', function() {
          window.close();
        });
      };

      container = document.createElement( 'div' );
      document.body.appendChild( container );

      // camera
      camera = new THREE.PerspectiveCamera( 75, SCREEN_WIDTH / SCREEN_HEIGHT, 1, 100000 );
      camera.position.z = 75;

      //scene
      scene = new THREE.Scene();

      // lights
      var ambient = new THREE.AmbientLight( 0xffffff );
      scene.add( ambient );

      // more lights
      var directionalLight = new THREE.DirectionalLight( 0xffeedd );
      directionalLight.position.set( 0, -70, 100 ).normalize();
      scene.add( directionalLight );

      // renderer
      webglRenderer = new THREE.WebGLRenderer();
      webglRenderer.setSize( SCREEN_WIDTH, SCREEN_HEIGHT );
      webglRenderer.domElement.style.position = "relative";
      container.appendChild( webglRenderer.domElement );

      // load ascii model
      var jsonLoader = new THREE.JSONLoader();
      jsonLoader.load( "obj/html5rocks.js", function( geometry ) { createScene( geometry ) } );

    }

    function createScene( geometry ) {
      zmesh = new THREE.Mesh( geometry, new THREE.MeshFaceMaterial() );
      zmesh.position.set( -10, -10, 0 );
      zmesh.scale.set( 1, 1, 1 );
      scene.add( zmesh );
    }

    function onDocumentMouseDown(event) {
      document.body.requestPointerLock =
        document.body.requestPointerLock ||
        document.body.mozRequestPointerLock ||
        document.body.webkitRequestPointerLock;
      document.body.requestPointerLock();
    }

    function onDocumentMouseUp(event) {
      document.exitPointerLock =
        document.exitPointerLock ||
        document.mozExitPointerLock ||
        document.webkitExitPointerLock;
      document.exitPointerLock();
    }

    function onDocumentMouseWheel(event) {
      camera.position.z -= event.wheelDelta/120*3;
    }

    function onDocumentMouseMove(event) {
      mouseX = ( event.clientX - windowHalfX );
      mouseY = ( event.clientY - windowHalfY );
      document.pointerLockElement =
        document.pointerLockElement ||
        document.mozPointerLockElement ||
        document.webkitPointerLockElement;
      if (document.pointerLockElement) {
        mousemoveX += event.movementX || event.mozMovementX || event.webkitMovementX || 0;
        mousemoveY += event.movementY || event.mozMovementY || event.webkitMovementY || 0;
      }
    }

    function animate() {
      requestAnimationFrame( animate );
      render();
    }

    function render() {

      if (zmesh) {
        zmesh.rotation.set(-(mouseY + mousemoveY)/windowHalfY + 0,
                           -(mouseX + mousemoveX)/windowHalfX, 0);
      }
      webglRenderer.render( scene, camera );
    }

});

<|endoftext|>


<commit_before>sc_require('views/button');

/**
  Describes the time after which "click and hold" behavior will replace the
  standard "click and release" behavior.
*/

/** @class

  SC.PopupButtonView displays a pop-up menu when clicked, from which the user
  can select an item.

  To use, create the SC.PopupButtonView as you would a standard SC.ButtonView,
  then set the menu property to an instance of SC.MenuPane. For example:

{{{
SC.PopupButtonView.design({
  layout: { width: 200, height: 18 },
  menuBinding: 'MyApp.menuController.menuPane'
});
}}}

  You would then have your MyApp.menuController return an instance of the menu
  to display.

  @extends SC.ButtonView
  @author Santosh Shanbhogue
  @author Tom Dale
  @copyright 2008-2010, Sprout Systems, Inc. and contributors.
  @version 1.0
*/
SC.PopupButtonView = SC.ButtonView.extend(
/** @scope SC.PopupButtonView.prototype */ {
  classNames: ['sc-popup-button'],

  // ..........................................................
  // PROPERTIES
  //

  /**
    The prefer matrix to use when displaying the menu.

    @property
  */
  preferMatrix: null,

  /**
    The SC.MenuPane that should be displayed when the button is clicked.

    @type {SC.MenuPane}
    @default null
  */
  menu: null,

  /**
    If YES and the menu is a class, this will cause a task that will instantiate the menu
    to be added to SC.backgroundTaskQueue.
  */
  shouldLoadInBackground: NO,

  // ..........................................................
  // INTERNAL SUPPORT
  //

  /**
    @private
    If necessary, adds the loading of the menu to the background task queue.
  */
  init: function() {
    sc_super();
    this._setupMenu();
    if (this.get("shouldLoadInBackground")) {
      SC.backgroundTaskQueue.push(SC.PopupButtonMenuLoader.create({ popupButton: this }));
    }
  },

  /**
    @private
    Sets up binding on the menu, removing any old ones if necessary.
  */
  _setupMenu: function() {
    var menu = this.get("instantiatedMenu");

    // clear existing bindings
    if (this.isActiveBinding) this.isActiveBinding.disconnect();
    this.isActiveBinding = null;

    // if there is a menu
    if (menu && !menu.isClass) {
      this.isActiveBinding = this.bind("isActive", menu, "isVisibleInWindow");
    }
  },

  /**
    Setup the bindings for menu...
  */
  _popup_menuDidChange: function() {
    this._setupMenu();
  }.observes("menu"),

  /** @private
    isActive is NO, but when the menu is instantiated, it is bound to the menu's isVisibleInWindow property.
  */
  isActive: NO,

  /**
    @private
    Instantiates the menu if it is not already instantiated.
  */
  _instantiateMenu: function() {
    // get menu
    var menu = this.get("menu");

    // if it is already instantiated or does not exist, we cannot do anything
    if (!menu.isClass || !menu) return;

    // create
    this.menu = menu.create();

    // setup
    this._setupMenu();
  },

  acceptsFirstResponder: YES,

  /**
    The guaranteed-instantiated menu.
  */
  instantiatedMenu: function() {
    // get the menu
    var menu = this.get("menu");

    // if it is a class, we need to instantiate it
    if (menu && menu.isClass) {
      // do so
      this._instantiateMenu();

      // get the new version of the local
      menu = this.get("menu");
    }

    // return
    return menu;
  }.property("menu").cacheable(),

  /** @private
    Displays the menu.

    @param {SC.Event} evt
  */
  action: function(evt)
  {
    var menu = this.get('instantiatedMenu') ;

    if (!menu) {
      //@ if (debug)
      SC.Logger.warn("SC.PopupButton - Unable to show menu because the menu property is set to %@.".fmt(menu));
      //@ endif
      return NO ;
    }

    menu.popup(this, this.get('preferMatrix')) ;
    return YES;
  },

  /** @private
    On mouse down, we set the state of the button, save some state for further
    processing, then call the button's action method.

    @param {SC.Event} evt
    @returns {Boolean}
  */
  mouseDown: function(evt) {
    // If disabled, handle mouse down but ignore it.
    if (!this.get('isEnabled')) return YES ;

    this._isMouseDown = YES;

    this._action() ;

    // Store the current timestamp. We register the timestamp at the end of
    // the runloop so that the menu has been rendered, in case that operation
    // takes more than a few hundred milliseconds.

    // One mouseUp, we'll use this value to determine how long the mouse was
    // pressed.
    this.invokeLast(this._recordMouseDownTimestamp);

    this.becomeFirstResponder();

    return YES ;
  },

  /** @private
    Records the current timestamp. This is invoked at the end of the runloop
    by mouseDown. We use this value to determine the delay between mouseDown
    and mouseUp.
  */
  _recordMouseDownTimestamp: function() {
    this._menuRenderedTimestamp = new Date().getTime();
  },

  /** @private
    Because we responded YES to the mouseDown event, we have responsibility
    for handling the corresponding mouseUp event.

    However, the user may click on this button, then drag the mouse down to a
    menu item, and release the mouse over the menu item. We therefore need to
    delegate any mouseUp events to the menu's menu item, if one is selected.

    We also need to differentiate between a single click and a click and hold.
    If the user clicks and holds, we want to close the menu when they release.
    Otherwise, we should wait until they click on the menu's modal pane before
    removing our active state.

    @param {SC.Event} evt
    @returns {Boolean}
  */
  mouseUp: function(evt) {
    var timestamp = new Date().getTime(),
        previousTimestamp = this._menuRenderedTimestamp,
        menu = this.get('instantiatedMenu'),
        touch = SC.platform.touch,
        targetMenuItem;

    if (menu) {
      // Get the menu item the user is currently hovering their mouse over
      targetMenuItem = menu.getPath('rootMenu.targetMenuItem');

      if (targetMenuItem) {
        // Have the menu item perform its action.
        // If the menu returns NO, it had no action to
        // perform, so we should close the menu immediately.
        if (!targetMenuItem.performAction()) menu.remove();
      } else {
        // If the user waits more than certain amount of time between
        // mouseDown and mouseUp, we can assume that they are clicking and
        // dragging to the menu item, and we should close the menu if they
        //mouseup anywhere not inside the menu.
        if (!touch && (timestamp - previousTimestamp > SC.ButtonView.CLICK_AND_HOLD_DELAY)) {
          menu.remove();
        }
      }
    }

    // Reset state.
    this._isMouseDown = NO;
    sc_super();
    return YES;
  },

  /** @private
    Overrides ButtonView's mouseExited method to remove the behavior where the
    active state is removed on mouse exit. We want the button to remain active
    as long as the menu is visible.

    @param {SC.Event} evt
    @returns {Boolean}
  */
  mouseExited: function(evt) {
    return YES;
  },

  /** @private
    Overrides performKeyEquivalent method to pass any keyboard shortcuts to
    the menu.

    @param {String} charCode string corresponding to shortcut pressed (e.g.,
    alt_shift_z)
    @param {SC.Event} evt
  */
  performKeyEquivalent: function( charCode, evt )
  {
    if (!this.get('isEnabled')) return NO ;
    var menu = this.get('instantiatedMenu') ;
    return (!!menu && menu.performKeyEquivalent(charCode, evt)) ;
  },

  /** @private */
  acceptsFirstResponder: function() {
    if(!SC.SAFARI_FOCUS_BEHAVIOR) return this.get('isEnabled');
    else return NO;
  }.property('isEnabled')

});

/**
  @private
  Handles lazy instantiation of popup button menu.
*/
SC.PopupButtonMenuLoader = SC.Task.extend({
  popupButton: null,
  run: function() {
    if (this.popupButton) this.popupButton._instantiateMenu();
  }
});

<commit_msg>It is idiomatic in SproutCore to use single quotes for property names and key values.<commit_after>sc_require('views/button');

/**
  Describes the time after which "click and hold" behavior will replace the
  standard "click and release" behavior.
*/

/** @class

  SC.PopupButtonView displays a pop-up menu when clicked, from which the user
  can select an item.

  To use, create the SC.PopupButtonView as you would a standard SC.ButtonView,
  then set the menu property to an instance of SC.MenuPane. For example:

{{{
SC.PopupButtonView.design({
  layout: { width: 200, height: 18 },
  menuBinding: 'MyApp.menuController.menuPane'
});
}}}

  You would then have your MyApp.menuController return an instance of the menu
  to display.

  @extends SC.ButtonView
  @author Santosh Shanbhogue
  @author Tom Dale
  @copyright 2008-2010, Sprout Systems, Inc. and contributors.
  @version 1.0
*/
SC.PopupButtonView = SC.ButtonView.extend(
/** @scope SC.PopupButtonView.prototype */ {
  classNames: ['sc-popup-button'],

  // ..........................................................
  // PROPERTIES
  //

  /**
    The prefer matrix to use when displaying the menu.

    @property
  */
  preferMatrix: null,

  /**
    The SC.MenuPane that should be displayed when the button is clicked.

    @type {SC.MenuPane}
    @default null
  */
  menu: null,

  /**
    If YES and the menu is a class, this will cause a task that will instantiate the menu
    to be added to SC.backgroundTaskQueue.
  */
  shouldLoadInBackground: NO,

  // ..........................................................
  // INTERNAL SUPPORT
  //

  /**
    @private
    If necessary, adds the loading of the menu to the background task queue.
  */
  init: function() {
    sc_super();
    this._setupMenu();
    if (this.get('shouldLoadInBackground')) {
      SC.backgroundTaskQueue.push(SC.PopupButtonMenuLoader.create({ popupButton: this }));
    }
  },

  /**
    @private
    Sets up binding on the menu, removing any old ones if necessary.
  */
  _setupMenu: function() {
    var menu = this.get('instantiatedMenu');

    // clear existing bindings
    if (this.isActiveBinding) this.isActiveBinding.disconnect();
    this.isActiveBinding = null;

    // if there is a menu
    if (menu && !menu.isClass) {
      this.isActiveBinding = this.bind('isActive', menu, 'isVisibleInWindow');
    }
  },

  /**
    Setup the bindings for menu...
  */
  _popup_menuDidChange: function() {
    this._setupMenu();
  }.observes('menu'),

  /** @private
    isActive is NO, but when the menu is instantiated, it is bound to the menu's isVisibleInWindow property.
  */
  isActive: NO,

  /**
    @private
    Instantiates the menu if it is not already instantiated.
  */
  _instantiateMenu: function() {
    // get menu
    var menu = this.get('menu');

    // if it is already instantiated or does not exist, we cannot do anything
    if (!menu.isClass || !menu) return;

    // create
    this.menu = menu.create();

    // setup
    this._setupMenu();
  },

  acceptsFirstResponder: YES,

  /**
    The guaranteed-instantiated menu.
  */
  instantiatedMenu: function() {
    // get the menu
    var menu = this.get('menu');

    // if it is a class, we need to instantiate it
    if (menu && menu.isClass) {
      // do so
      this._instantiateMenu();

      // get the new version of the local
      menu = this.get('menu');
    }

    // return
    return menu;
  }.property('menu').cacheable(),

  /** @private
    Displays the menu.

    @param {SC.Event} evt
  */
  action: function(evt)
  {
    var menu = this.get('instantiatedMenu') ;

    if (!menu) {
      //@ if (debug)
      SC.Logger.warn("SC.PopupButton - Unable to show menu because the menu property is set to %@.".fmt(menu));
      //@ endif
      return NO ;
    }

    menu.popup(this, this.get('preferMatrix')) ;
    return YES;
  },

  /** @private
    On mouse down, we set the state of the button, save some state for further
    processing, then call the button's action method.

    @param {SC.Event} evt
    @returns {Boolean}
  */
  mouseDown: function(evt) {
    // If disabled, handle mouse down but ignore it.
    if (!this.get('isEnabled')) return YES ;

    this._isMouseDown = YES;

    this._action() ;

    // Store the current timestamp. We register the timestamp at the end of
    // the runloop so that the menu has been rendered, in case that operation
    // takes more than a few hundred milliseconds.

    // One mouseUp, we'll use this value to determine how long the mouse was
    // pressed.
    this.invokeLast(this._recordMouseDownTimestamp);

    this.becomeFirstResponder();

    return YES ;
  },

  /** @private
    Records the current timestamp. This is invoked at the end of the runloop
    by mouseDown. We use this value to determine the delay between mouseDown
    and mouseUp.
  */
  _recordMouseDownTimestamp: function() {
    this._menuRenderedTimestamp = new Date().getTime();
  },

  /** @private
    Because we responded YES to the mouseDown event, we have responsibility
    for handling the corresponding mouseUp event.

    However, the user may click on this button, then drag the mouse down to a
    menu item, and release the mouse over the menu item. We therefore need to
    delegate any mouseUp events to the menu's menu item, if one is selected.

    We also need to differentiate between a single click and a click and hold.
    If the user clicks and holds, we want to close the menu when they release.
    Otherwise, we should wait until they click on the menu's modal pane before
    removing our active state.

    @param {SC.Event} evt
    @returns {Boolean}
  */
  mouseUp: function(evt) {
    var timestamp = new Date().getTime(),
        previousTimestamp = this._menuRenderedTimestamp,
        menu = this.get('instantiatedMenu'),
        touch = SC.platform.touch,
        targetMenuItem;

    if (menu) {
      // Get the menu item the user is currently hovering their mouse over
      targetMenuItem = menu.getPath('rootMenu.targetMenuItem');

      if (targetMenuItem) {
        // Have the menu item perform its action.
        // If the menu returns NO, it had no action to
        // perform, so we should close the menu immediately.
        if (!targetMenuItem.performAction()) menu.remove();
      } else {
        // If the user waits more than certain amount of time between
        // mouseDown and mouseUp, we can assume that they are clicking and
        // dragging to the menu item, and we should close the menu if they
        //mouseup anywhere not inside the menu.
        if (!touch && (timestamp - previousTimestamp > SC.ButtonView.CLICK_AND_HOLD_DELAY)) {
          menu.remove();
        }
      }
    }

    // Reset state.
    this._isMouseDown = NO;
    sc_super();
    return YES;
  },

  /** @private
    Overrides ButtonView's mouseExited method to remove the behavior where the
    active state is removed on mouse exit. We want the button to remain active
    as long as the menu is visible.

    @param {SC.Event} evt
    @returns {Boolean}
  */
  mouseExited: function(evt) {
    return YES;
  },

  /** @private
    Overrides performKeyEquivalent method to pass any keyboard shortcuts to
    the menu.

    @param {String} charCode string corresponding to shortcut pressed (e.g.,
    alt_shift_z)
    @param {SC.Event} evt
  */
  performKeyEquivalent: function( charCode, evt )
  {
    if (!this.get('isEnabled')) return NO ;
    var menu = this.get('instantiatedMenu') ;
    return (!!menu && menu.performKeyEquivalent(charCode, evt)) ;
  },

  /** @private */
  acceptsFirstResponder: function() {
    if(!SC.SAFARI_FOCUS_BEHAVIOR) return this.get('isEnabled');
    else return NO;
  }.property('isEnabled')

});

/**
  @private
  Handles lazy instantiation of popup button menu.
*/
SC.PopupButtonMenuLoader = SC.Task.extend({
  popupButton: null,
  run: function() {
    if (this.popupButton) this.popupButton._instantiateMenu();
  }
});

<|endoftext|>


<commit_before>/**
 * logdown - Debug utility with markdown support that runs on browser and server
 *
 * @version v1.0.7
 * @link https://github.com/caiogondim/logdown
 * @author Caio Gondim <me@caiogondim.com> (http://caiogondim.com)
 * @license ISC
 */
!function(){"use strict";function e(r){if(!(this instanceof e))return new e(r);r=r||{};var o=void 0===r.prefix?"":r.prefix;return o=a(o),o&&l(o,x)?c(o,x):(this.markdown=void 0===r.markdown?!0:r.markdown,this.prefix=o,x.push(this),f()?(this.prefixColor=m[g%m.length],g+=1):u()&&(this.prefixColor=v()),this)}function r(e){for(var r=[],t=o(e);t;)e=e.replace(t.rule.regexp,t.rule.replacer),f()&&(r.push(t.rule.style),r.push("color:inherit;")),t=o(e);return{text:e,styles:r}}function o(e){var r=[],o=[];return f()?o=[{regexp:/\*([^\*]+)\*/,replacer:function(e,r){return"%c"+r+"%c"},style:"font-weight:bold;"},{regexp:/\_([^\_]+)\_/,replacer:function(e,r){return"%c"+r+"%c"},style:"font-style:italic;"},{regexp:/\`([^\`]+)\`/,replacer:function(e,r){return"%c"+r+"%c"},style:"background:#FDF6E3; color:#586E75; padding:1px 5px; border-radius:4px;"}]:u()&&(o=[{regexp:/\*([^\*]+)\*/,replacer:function(e,r){return"["+b.modifiers.bold[0]+"m"+r+"["+b.modifiers.bold[1]+"m"}},{regexp:/\_([^\_]+)\_/,replacer:function(e,r){return"["+b.modifiers.italic[0]+"m"+r+"["+b.modifiers.italic[1]+"m"}},{regexp:/\`([^\`]+)\`/,replacer:function(e,r){return"["+b.bgColors.bgYellow[0]+"m["+b.colors.black[0]+"m "+r+" ["+b.colors.black[1]+"m["+b.bgColors.bgYellow[1]+"m"}}]),o.forEach(function(o){var t=e.match(o.regexp);t&&r.push({rule:o,match:t})}),0===r.length?null:(r.sort(function(e,r){return e.match.index-r.match.index}),r[0])}function t(e,o){var t,n,i,s;return"string"==typeof e?o.markdown&&p()?(t=r(e),n=t.text,i=t.styles):(n=e,i=[]):(n=n||"",i=i||[],s=e),o.prefix&&(p()?(n="%c"+o.prefix+"%c "+n,i.unshift("color:"+o.prefixColor+"; font-weight:bold;","color:inherit;")):n="["+o.prefix+"] "+n),{parsedText:n,styles:i,notText:s}}function n(e,o){var t,n="";return o.prefix&&(n=p()?"["+o.prefixColor[0]+"m["+b.modifiers.bold[0]+"m"+o.prefix+"["+b.modifiers.bold[1]+"m["+o.prefixColor[1]+"m ":"["+o.prefix+"] "),"string"==typeof e?n+=o.markdown?r(e).text:e:t=e,{parsedText:n,styles:[],notText:t}}function i(e){var r=!1;return h.forEach(function(o){"enable"===o.type&&o.regExp.test(e.prefix)?r=!1:"disable"===o.type&&o.regExp.test(e.prefix)&&(r=!0)}),r}function s(e){return new RegExp("^"+e.replace(/\*/g,".*?")+"$")}function l(e,r){var o=!1;return r.forEach(function(r){return r.prefix===e?void(o=!0):void 0}),o}function c(e,r){var o;return r.forEach(function(r){return r.prefix===e?void(o=r):void 0}),o}function a(e){return"string"==typeof e?e.replace(/\%c/g,""):e}function p(){if(f()){var e="WebkitAppearance"in document.documentElement.style,r=window.console&&(console.firebug||console.exception&&console.table),o=navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31;return e||r||o}return u()?process.stdout&&!process.stdout.isTTY?!1:"win32"===process.platform?!0:"COLORTERM"in process.env?!0:"dumb"===process.env.TERM?!1:/^screen|^xterm|^vt100|color|ansi|cygwin|linux/i.test(process.env.TERM)?!0:!1:void 0}function u(){return"undefined"!=typeof module&&"undefined"!=typeof module.exports}function f(){return"undefined"!=typeof window}function d(e){return e}var x=[],g=0,m=["#B58900","#CB4B16","#DC322F","#D33682","#6C71C4","#268BD2","#2AA198","#859900"],b={modifiers:{reset:[0,0],bold:[1,22],dim:[2,22],italic:[3,23],underline:[4,24],inverse:[7,27],hidden:[8,28],strikethrough:[9,29]},colors:{black:[30,39],red:[31,39],green:[32,39],yellow:[33,39],blue:[34,39],magenta:[35,39],cyan:[36,39],white:[37,39],gray:[90,39]},bgColors:{bgBlack:[40,49],bgRed:[41,49],bgGreen:[42,49],bgYellow:[43,49],bgBlue:[44,49],bgMagenta:[45,49],bgCyan:[46,49],bgWhite:[47,49]}},h=[];e.enable=function(){Array.prototype.forEach.call(arguments,function(r){"-"===r[0]&&e.disable(r.substr(1));var o=s(r);"*"===r?h=[{type:"enable",regExp:o}]:h.push({type:"enable",regExp:o})})},e.disable=function(){Array.prototype.forEach.call(arguments,function(r){"-"===r[0]&&e.enable(r.substr(1));var o=s(r);"*"===r?h=[{type:"disable",regExp:o}]:h.push({type:"disable",regExp:o})})};var y=["log","info","warn","error"];y.forEach(function(r){e.prototype[r]=function(e){var o,s=[];i(this)||(f()?(e=a(e),o=t(e,this),Function.prototype.apply.call(console[r],console,[o.parsedText].concat(o.styles,[o.notText]))):u()&&(e=d(e),o=n(e,this),"warn"===r?o.parsedText="["+b.colors.yellow[0]+"m⚠["+b.colors.yellow[1]+"m "+o.parsedText:"error"===r?o.parsedText="["+b.colors.red[0]+"m✖["+b.colors.red[1]+"m "+o.parsedText:"info"===r&&(o.parsedText="["+b.colors.blue[0]+"mℹ["+b.colors.blue[1]+"m "+o.parsedText),s.push(o.parsedText),o.notText&&s.push(o.notText),console[r].apply(console,s)))}});var v=function(){var e=0,r=[[31,39],[32,39],[33,39],[34,39],[35,39],[36,39]];return function(){return r[(e+=1)%r.length]}}();u()?module.exports=e:f()&&(window.Logdown=e)}();<commit_msg>Bump to v1.0.8<commit_after>/**
 * logdown - Debug utility with markdown support that runs on browser and server
 *
 * @version v1.0.8
 * @link https://github.com/caiogondim/logdown
 * @author Caio Gondim <me@caiogondim.com> (http://caiogondim.com)
 * @license ISC
 */
!function(){"use strict";function e(r){if(!(this instanceof e))return new e(r);r=r||{};var o=void 0===r.prefix?"":r.prefix;return o=a(o),o&&l(o,x)?c(o,x):(this.markdown=void 0===r.markdown?!0:r.markdown,this.prefix=o,x.push(this),f()?(this.prefixColor=m[g%m.length],g+=1):u()&&(this.prefixColor=v()),this)}function r(e){for(var r=[],t=o(e);t;)e=e.replace(t.rule.regexp,t.rule.replacer),f()&&(r.push(t.rule.style),r.push("color:inherit;")),t=o(e);return{text:e,styles:r}}function o(e){var r=[],o=[];return f()?o=[{regexp:/\*([^\*]+)\*/,replacer:function(e,r){return"%c"+r+"%c"},style:"font-weight:bold;"},{regexp:/\_([^\_]+)\_/,replacer:function(e,r){return"%c"+r+"%c"},style:"font-style:italic;"},{regexp:/\`([^\`]+)\`/,replacer:function(e,r){return"%c"+r+"%c"},style:"background:#FDF6E3; color:#586E75; padding:1px 5px; border-radius:4px;"}]:u()&&(o=[{regexp:/\*([^\*]+)\*/,replacer:function(e,r){return"["+b.modifiers.bold[0]+"m"+r+"["+b.modifiers.bold[1]+"m"}},{regexp:/\_([^\_]+)\_/,replacer:function(e,r){return"["+b.modifiers.italic[0]+"m"+r+"["+b.modifiers.italic[1]+"m"}},{regexp:/\`([^\`]+)\`/,replacer:function(e,r){return"["+b.bgColors.bgYellow[0]+"m["+b.colors.black[0]+"m "+r+" ["+b.colors.black[1]+"m["+b.bgColors.bgYellow[1]+"m"}}]),o.forEach(function(o){var t=e.match(o.regexp);t&&r.push({rule:o,match:t})}),0===r.length?null:(r.sort(function(e,r){return e.match.index-r.match.index}),r[0])}function t(e,o){var t,n,i,s;return"string"==typeof e?o.markdown&&p()?(t=r(e),n=t.text,i=t.styles):(n=e,i=[]):(n=n||"",i=i||[],s=e),o.prefix&&(p()?(n="%c"+o.prefix+"%c "+n,i.unshift("color:"+o.prefixColor+"; font-weight:bold;","color:inherit;")):n="["+o.prefix+"] "+n),{parsedText:n,styles:i,notText:s}}function n(e,o){var t,n="";return o.prefix&&(n=p()?"["+o.prefixColor[0]+"m["+b.modifiers.bold[0]+"m"+o.prefix+"["+b.modifiers.bold[1]+"m["+o.prefixColor[1]+"m ":"["+o.prefix+"] "),"string"==typeof e?n+=o.markdown?r(e).text:e:t=e,{parsedText:n,styles:[],notText:t}}function i(e){var r=!1;return h.forEach(function(o){"enable"===o.type&&o.regExp.test(e.prefix)?r=!1:"disable"===o.type&&o.regExp.test(e.prefix)&&(r=!0)}),r}function s(e){return new RegExp("^"+e.replace(/\*/g,".*?")+"$")}function l(e,r){var o=!1;return r.forEach(function(r){return r.prefix===e?void(o=!0):void 0}),o}function c(e,r){var o;return r.forEach(function(r){return r.prefix===e?void(o=r):void 0}),o}function a(e){return"string"==typeof e?e.replace(/\%c/g,""):e}function p(){if(f()){var e="WebkitAppearance"in document.documentElement.style,r=window.console&&(console.firebug||console.exception&&console.table),o=navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31;return e||r||o}return u()?process.stdout&&!process.stdout.isTTY?!1:"win32"===process.platform?!0:"COLORTERM"in process.env?!0:"dumb"===process.env.TERM?!1:/^screen|^xterm|^vt100|color|ansi|cygwin|linux/i.test(process.env.TERM)?!0:!1:void 0}function u(){return"undefined"!=typeof module&&"undefined"!=typeof module.exports}function f(){return"undefined"!=typeof window}function d(e){return e}var x=[],g=0,m=["#B58900","#CB4B16","#DC322F","#D33682","#6C71C4","#268BD2","#2AA198","#859900"],b={modifiers:{reset:[0,0],bold:[1,22],dim:[2,22],italic:[3,23],underline:[4,24],inverse:[7,27],hidden:[8,28],strikethrough:[9,29]},colors:{black:[30,39],red:[31,39],green:[32,39],yellow:[33,39],blue:[34,39],magenta:[35,39],cyan:[36,39],white:[37,39],gray:[90,39]},bgColors:{bgBlack:[40,49],bgRed:[41,49],bgGreen:[42,49],bgYellow:[43,49],bgBlue:[44,49],bgMagenta:[45,49],bgCyan:[46,49],bgWhite:[47,49]}},h=[];e.enable=function(){Array.prototype.forEach.call(arguments,function(r){"-"===r[0]&&e.disable(r.substr(1));var o=s(r);"*"===r?h=[{type:"enable",regExp:o}]:h.push({type:"enable",regExp:o})})},e.disable=function(){Array.prototype.forEach.call(arguments,function(r){"-"===r[0]&&e.enable(r.substr(1));var o=s(r);"*"===r?h=[{type:"disable",regExp:o}]:h.push({type:"disable",regExp:o})})};var y=["log","info","warn","error"];y.forEach(function(r){e.prototype[r]=function(e){var o,s=[];i(this)||(f()?(e=a(e),o=t(e,this),Function.prototype.apply.call(console[r],console,[o.parsedText].concat(o.styles,[o.notText]))):u()&&(e=d(e),o=n(e,this),"warn"===r?o.parsedText="["+b.colors.yellow[0]+"m⚠["+b.colors.yellow[1]+"m "+o.parsedText:"error"===r?o.parsedText="["+b.colors.red[0]+"m✖["+b.colors.red[1]+"m "+o.parsedText:"info"===r&&(o.parsedText="["+b.colors.blue[0]+"mℹ["+b.colors.blue[1]+"m "+o.parsedText),s.push(o.parsedText),o.notText&&s.push(o.notText),console[r].apply(console,s)))}});var v=function(){var e=0,r=[[31,39],[32,39],[33,39],[34,39],[35,39],[36,39]];return function(){return r[(e+=1)%r.length]}}();u()?module.exports=e:f()&&(window.Logdown=e)}();<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import SPELLS from 'common/SPELLS';
import Checklist from 'parser/shared/modules/features/Checklist';
import Rule from 'parser/shared/modules/features/Checklist/Rule';
import PreparationRule from 'parser/shared/modules/features/Checklist/PreparationRule';
import Requirement from 'parser/shared/modules/features/Checklist/Requirement';
import GenericCastEfficiencyRequirement from 'parser/shared/modules/features/Checklist/GenericCastEfficiencyRequirement';
import SpellLink from 'common/SpellLink';

class ElementalShamanChecklist extends React.PureComponent {
  static propTypes = {
    castEfficiency: PropTypes.object.isRequired,
    combatant: PropTypes.shape({
      hasTalent: PropTypes.func.isRequired,
      hasTrinket: PropTypes.func.isRequired,
    }).isRequired,
    thresholds: PropTypes.object.isRequired,
  };

  render() {
    const { combatant, castEfficiency, thresholds } = this.props;
    const AbilityRequirement = props => (
      <GenericCastEfficiencyRequirement
        castEfficiency={castEfficiency.getCastEfficiencyForSpellId(props.spell)}
        {...props}
      />
    );

    return (
      <Checklist>
        <Rule
          name="Use core abilities as often as possible"
          description={(
            <>
              Using your core abilities as often as possible can help raise your dps significantly. Some help more than others, but as a general rule of thumb you should be looking to use most of your damaging abilities and damage cooldowns as often as possible, unless you need to save them for a priority burst phase that is coming up soon.
              {'\u00a0'}
              More info.
            </>
          )}
        >
          {combatant.hasTalent(SPELLS.ASCENDANCE_TALENT_ELEMENTAL.id) && <AbilityRequirement spell={SPELLS.ASCENDANCE_TALENT_ELEMENTAL.id} />}
          {!combatant.hasTalent(SPELLS.STORM_ELEMENTAL_TALENT.id) && <AbilityRequirement spell={SPELLS.FIRE_ELEMENTAL.id} />}
          {combatant.hasTalent(SPELLS.STORM_ELEMENTAL_TALENT.id) && <AbilityRequirement spell={SPELLS.STORM_ELEMENTAL_TALENT.id} />}
          {combatant.hasTalent(SPELLS.ICEFURY_TALENT.id) && <AbilityRequirement spell={SPELLS.ICEFURY_TALENT.id} />}
          {combatant.hasTalent(SPELLS.STORMKEEPER_TALENT.id) && <AbilityRequirement spell={SPELLS.STORMKEEPER_TALENT.id} />}
          {combatant.hasTalent(SPELLS.LIQUID_MAGMA_TOTEM_TALENT.id) && <AbilityRequirement spell={SPELLS.LIQUID_MAGMA_TOTEM_TALENT.id} />}
          {combatant.hasTalent(SPELLS.ELEMENTAL_BLAST_TALENT.id) && <AbilityRequirement spell={SPELLS.ELEMENTAL_BLAST_TALENT.id} />}
        </Rule>
        <Rule
          name="Maintain your flame shock."
          description={(
            <>
              It's important to maintain flame shock on your target to guarantee <SpellLink id={SPELLS.LAVA_BURST.id} /> will crit and to allow for <SpellLink id={SPELLS.LAVA_SURGE.id} /> procs.

              Applying <SpellLink id={SPELLS.FLAME_SHOCK.id} /> itself doesn't do much damage so you should only refresh it with 30% (about 7 seconds) or less of it's total duration remaining to beneift from pandemic.
            </>
          )}
        >
          <Requirement
            name={(
              <>
                <SpellLink id={SPELLS.FLAME_SHOCK.id} /> uptime
              </>
            )}
            thresholds={thresholds.flameShockUptime}
          />
          <Requirement
            name={(
              <>
                Bad <SpellLink id={SPELLS.FLAME_SHOCK.id} /> refreshes
              </>
            )}
            thresholds={thresholds.flameShockRefreshes}
          />

        </Rule>
        <Rule
          name="Minimize Downtime"
          description={(
            <>
              Downtime is the time where you are not casting (and not on GCD) or are on GCD from a cancelled cast. Ensure you are casting as much as possible by avoiding movement when you could be casting. Elemental shaman has many GCDs available from <SpellLink id={SPELLS.EARTH_SHOCK.id} />, <SpellLink id={SPELLS.LAVA_SURGE.id} /> empowered <SpellLink id={SPELLS.LAVA_BURST.id} />s, <SpellLink id={SPELLS.FROST_SHOCK.id} />, and others that help you move towards your location without incurring downtime. Additionally, cancelled casts contribute significantly as they fill a GCD without actually doing damage. It's expected that some casts will need to be cancelled due to mechanics, but proper planning can help mitigate that.
            </>
          )}
        >
          <Requirement name="Downtime" thresholds={thresholds.downtime} />
          <Requirement name="Cancelled casts" thresholds={thresholds.cancelledCasts} />
        </Rule>
        {combatant.hasTalent(SPELLS.ICEFURY_TALENT.id) && (
        <Rule
          name={<>Utilize all Icefury Stacks</>}
          description={(
            <>
              <SpellLink id={SPELLS.ICEFURY_TALENT.id} />'s damage component itself is not a strong spell so it's important to fully utilize the talent by consuming all 4 <SpellLink id={SPELLS.ICEFURY_TALENT.id} /> buff stacks with <SpellLink id={SPELLS.FROST_SHOCK.id} /> casts during the buff's duration.
              {combatant.hasTalent(SPELLS.MASTER_OF_THE_ELEMENTS_TALENT.id) && <> While you should try to buff as many <SpellLink id={SPELLS.ICEFURY_TALENT.id} /> empowered <SpellLink id={SPELLS.FROST_SHOCK.id} /> as you can with <SpellLink id={SPELLS.MASTER_OF_THE_ELEMENTS_TALENT.id} />, it is far more important to actually use all 4 charges before the buff expires.</>}
            </>
          )}>
          <Requirement name={<>Average <SpellLink id={SPELLS.FROST_SHOCK.id} /> Casts within <SpellLink id={SPELLS.ICEFURY_TALENT.id} /> Duration</>} thresholds={thresholds.icefuryEfficiency} />
        </Rule>
        )}
        <PreparationRule thresholds={thresholds} />
      </Checklist>
    );
  }
}

export default ElementalShamanChecklist;
<commit_msg>clean up checklist now that it's gotten long<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import SPELLS from 'common/SPELLS';
import Checklist from 'parser/shared/modules/features/Checklist';
import Rule from 'parser/shared/modules/features/Checklist/Rule';
import PreparationRule from 'parser/shared/modules/features/Checklist/PreparationRule';
import Requirement from 'parser/shared/modules/features/Checklist/Requirement';
import GenericCastEfficiencyRequirement from 'parser/shared/modules/features/Checklist/GenericCastEfficiencyRequirement';
import SpellLink from 'common/SpellLink';

class ElementalShamanChecklist extends React.PureComponent {
  static propTypes = {
    castEfficiency: PropTypes.object.isRequired,
    combatant: PropTypes.shape({
      hasTalent: PropTypes.func.isRequired,
      hasTrinket: PropTypes.func.isRequired,
    }).isRequired,
    thresholds: PropTypes.object.isRequired,
  };

  render() {
    return (
      <Checklist>
        {this.useCoreAbilitiesRule()}
        {this.minimizeDowntimeRule()}
        {this.maintainFlameShockRule()}
        {this.maximizeIcefuryRule()}
        <PreparationRule thresholds={this.props.thresholds} />
      </Checklist>
    );
  }

  useCoreAbilitiesRule() {
    const AbilityRequirement = props => (
      <GenericCastEfficiencyRequirement castEfficiency={this.props.castEfficiency.getCastEfficiencyForSpellId(props.spell)} {...props} />
    );

    const description = (
      <>
        Using your core abilities as often as possible can help raise your dps significantly. Some help more than others, but as a general rule of thumb you should be looking to use most of your damaging abilities and damage cooldowns as often as possible, unless you need to save them for a priority burst phase that is coming up soon.
        {'\u00a0'}
        More info.
      </>
    );

    const combatant = this.props.combatant;

    return (
      <Rule name="Use core abilities as often as possible" description={description}>
        {combatant.hasTalent(SPELLS.ASCENDANCE_TALENT_ELEMENTAL.id) && <AbilityRequirement spell={SPELLS.ASCENDANCE_TALENT_ELEMENTAL.id} />}
        {!combatant.hasTalent(SPELLS.STORM_ELEMENTAL_TALENT.id) && <AbilityRequirement spell={SPELLS.FIRE_ELEMENTAL.id} />}
        {combatant.hasTalent(SPELLS.STORM_ELEMENTAL_TALENT.id) && <AbilityRequirement spell={SPELLS.STORM_ELEMENTAL_TALENT.id} />}
        {combatant.hasTalent(SPELLS.ICEFURY_TALENT.id) && <AbilityRequirement spell={SPELLS.ICEFURY_TALENT.id} />}
        {combatant.hasTalent(SPELLS.STORMKEEPER_TALENT.id) && <AbilityRequirement spell={SPELLS.STORMKEEPER_TALENT.id} />}
        {combatant.hasTalent(SPELLS.LIQUID_MAGMA_TOTEM_TALENT.id) && <AbilityRequirement spell={SPELLS.LIQUID_MAGMA_TOTEM_TALENT.id} />}
        {combatant.hasTalent(SPELLS.ELEMENTAL_BLAST_TALENT.id) && <AbilityRequirement spell={SPELLS.ELEMENTAL_BLAST_TALENT.id} />}
      </Rule>
    );
  }

  minimizeDowntimeRule() {
    const description = (
      <>
        Downtime is the time where you are not casting (and not on GCD) or are on GCD from a cancelled cast. Ensure you are casting as much as possible by avoiding movement when you could be casting.
        Elemental shaman has many GCDs available from <SpellLink id={SPELLS.EARTH_SHOCK.id} />, <SpellLink id={SPELLS.LAVA_SURGE.id} /> empowered <SpellLink id={SPELLS.LAVA_BURST.id} />s, <SpellLink id={SPELLS.FROST_SHOCK.id} />, and others that help you move towards your location without incurring downtime.
        Additionally, cancelled casts contribute significantly as they fill a GCD without actually doing damage. It's expected that some casts will need to be cancelled due to mechanics, but proper planning can help mitigate that.
      </>
    );

    return (
      <Rule name="Minimize Downtime" description={description} >
        <Requirement name="Downtime" thresholds={this.props.thresholds.downtime} />
        <Requirement name="Cancelled casts" thresholds={this.props.thresholds.cancelledCasts} />
      </Rule>
    );
  }

  maintainFlameShockRule() {
    const description = (
      <>
        It's important to maintain flame shock on your target to guarantee <SpellLink id={SPELLS.LAVA_BURST.id} /> will crit and to allow for <SpellLink id={SPELLS.LAVA_SURGE.id} /> procs.

        Applying <SpellLink id={SPELLS.FLAME_SHOCK.id} /> itself doesn't do much damage so you should only refresh it with 30% (about 7 seconds) or less of it's total duration remaining to beneift from pandemic.
      </>
    );

    return (
      <Rule name="Maintain your flame shock." description={description}>
        <Requirement name={(<><SpellLink id={SPELLS.FLAME_SHOCK.id} /> uptime</> )} thresholds={this.props.thresholds.flameShockUptime} />
        <Requirement name={( <> Bad <SpellLink id={SPELLS.FLAME_SHOCK.id} /> refreshes </> )} thresholds={this.props.thresholds.flameShockRefreshes} />
      </Rule>
    );
  }

  maximizeIcefuryRule() {

    const description = (
      <>
        <SpellLink id={SPELLS.ICEFURY_TALENT.id} />'s damage component itself is not a strong spell so it's important to fully utilize the talent by consuming all 4 <SpellLink id={SPELLS.ICEFURY_TALENT.id} /> buff stacks with <SpellLink id={SPELLS.FROST_SHOCK.id} /> casts during the buff's duration.
        {this.props.combatant.hasTalent(SPELLS.MASTER_OF_THE_ELEMENTS_TALENT.id) && <> While you should try to buff as many <SpellLink id={SPELLS.ICEFURY_TALENT.id} /> empowered <SpellLink id={SPELLS.FROST_SHOCK.id} /> as you can with <SpellLink id={SPELLS.MASTER_OF_THE_ELEMENTS_TALENT.id} />, it is far more important to actually use all 4 charges before the buff expires.</>}
      </>
    );

    return this.props.combatant.hasTalent(SPELLS.ICEFURY_TALENT.id) ? (
      <Rule name="Utilize all Icefury Stacks" description={description}>
        <Requirement name={<>Average <SpellLink id={SPELLS.FROST_SHOCK.id} /> Casts within <SpellLink id={SPELLS.ICEFURY_TALENT.id} /> Duration</>} thresholds={this.props.thresholds.icefuryEfficiency} />
      </Rule>
    ) : '';
  }
}

export default ElementalShamanChecklist;
<|endoftext|>