<commit_before>var chai = require("chai"),
    jQuery = require("jQuery");

var expect = chai.expect;

var $ = global.jQuery = jQuery;

function createSelect() {
    $('<select />', { 'id':'rating', 'name':'rating' }).appendTo('body');

    for (var i = 1; i <= 10; i++) {
        var attributes = (i == 5) ?
            attributes = { 'value':i, 'selected':'selected' } :
            attributes = { 'value':i };

        $('<option />', attributes).appendTo('#rating').html('rating-text-'+i);
    }
}

function destroySelect() {
    $('#rating').remove();
}

require("../jquery.barrating");

describe('bar rating plugin on init with custom options', function () {

    it('should update defaults', function () {
        var BarRating;
        BarRating = new global.BarRating();
        BarRating.init({
            showValues: false
        });
        expect(BarRating.options).to.be.a('object');
        expect(BarRating.options.initialRating).to.equal(null);
        expect(BarRating.options.showValues).to.equal(false);
        expect(BarRating.options.showSelectedRating).to.equal(true);
        expect(BarRating.options.reverse).to.equal(false);
        expect(BarRating.options.readonly).to.equal(false);
    });

});

describe('bar rating plugin on show', function () {

    before(function () {
        createSelect();
        $('#rating').barrating('show');
    });

    after(function () {
        $('#rating').barrating('destroy');
        destroySelect();
    });

    it('should have data', function () {
        expect($('#rating').data('barrating')).to.be.a('object');
    });

    it('should transform the select field into a rating widget', function () {
        expect($('.br-widget a')).to.have.length(10);
    });

    it('should store rating values in data attributes', function () {
        expect($('.br-widget a:first').attr('data-rating-value')).to.equal('1');
        expect($('.br-widget a:nth-child(8)').attr('data-rating-value')).to.equal('8');
        expect($('.br-widget a:first').attr('data-rating-text')).to.equal('rating-text-1');
        expect($('.br-widget a:nth-child(8)').attr('data-rating-text')).to.equal('rating-text-8');
    });

    it('should read the selected rating from the select field', function () {
        expect($('#rating').data('barrating').currentRatingValue).to.equal('5');
        expect($('#rating').data('barrating').currentRatingText).to.equal('rating-text-5');
    });

    it('should set correct class', function () {
        expect($('.br-widget a:nth-child(4)').hasClass('br-selected')).to.equal(true);
        expect($('.br-widget a:nth-child(5)').hasClass('br-selected br-current')).to.equal(true);
        expect($('.br-widget a:nth-child(6)').hasClass('br-selected')).to.equal(false);
    });

    it('should append a rating div', function () {
        expect($('div.br-current-rating')).to.have.length(1);
    });

    it('should display a correct rating', function () {
        expect($('div.br-current-rating').html()).to.equal(
            $('#rating').data('barrating').currentRatingText
        );
    });

    it('should hide the select field', function () {
        expect($('#rating').is(":visible")).to.equal(false);
    });

});

describe('bar rating plugin on show and rating selected', function () {

    var valuesFromCallback = [];

    before(function () {
        createSelect();

        $('#rating').barrating('show', {
            onSelect:function (value, text) {
                valuesFromCallback.push(value, text);
            }
        });

        $('.br-widget a:nth-child(2)').trigger('click');
    });

    after(function () {
        $('#rating').barrating('destroy');
        destroySelect();
    });

    it('should update data', function () {
        expect($('#rating').data('barrating').currentRatingValue).to.equal('2');
        expect($('#rating').data('barrating').currentRatingText).to.equal('rating-text-2');
    });

    it('should set correct class', function () {
        expect($('.br-widget a:nth-child(1)').hasClass('br-selected')).to.equal(true);
        expect($('.br-widget a:nth-child(2)').hasClass('br-selected br-current')).to.equal(true);
        expect($('.br-widget a:nth-child(3)').hasClass('br-selected')).to.equal(false);
    });

    it('should display a correct rating', function () {
        expect($('div.br-current-rating').html()).to.equal(
            $('#rating').data('barrating').currentRatingText
        );
    });

    it('should pass correct values to a callback', function () {
        expect(valuesFromCallback[0]).to.equal('2');
        expect(valuesFromCallback[1]).to.equal('rating-text-2');
    });

});

describe('bar rating plugin reversed', function () {

    before(function () {
        createSelect();

        $('#rating').barrating('show', {
            reverse:true
        });
    });

    after(function () {
        $('#rating').barrating('destroy');
        destroySelect();
    });

    it('should set correct class', function () {
        expect($('.br-widget a:nth-child(4)').hasClass('br-selected')).to.equal(false);
        expect($('.br-widget a:nth-child(5)').hasClass('br-selected br-current')).to.equal(true);
        expect($('.br-widget a:nth-child(6)').hasClass('br-selected')).to.equal(true);
    });

});

describe('bar rating plugin read-only', function () {

    before(function () {
        createSelect();

        $('#rating').barrating('show', {
            readonly:true
        });

        $('.br-widget a:nth-child(6)').trigger('click');
    });

    after(function () {
        $('#rating').barrating('destroy');
        destroySelect();
    });

    it('should set correct class', function () {
        expect($('.br-widget a:nth-child(4)').hasClass('br-selected')).to.equal(true);
        expect($('.br-widget a:nth-child(5)').hasClass('br-selected br-current')).to.equal(true);
        expect($('.br-widget a:nth-child(6)').hasClass('br-selected')).to.equal(false);
    });

    it('should ignore user input', function () {
        expect($('#rating').data('barrating').currentRatingValue).to.equal('5');
        expect($('#rating').data('barrating').currentRatingText).to.equal('rating-text-5');
    });

});

describe('bar rating plugin on deselect', function () {

    before(function () {
        createSelect();

        // prepend empty OPTION to test deselectable ratings
        $('#rating').prepend($('<option />', { 'value':'' }));
        $('#rating').barrating('show');

        // deselect rating
        $('.br-widget a:nth-child(5)').trigger('click');
    });

    after(function () {
        $('#rating').barrating('destroy');
        destroySelect();
    });

    it('should update data', function () {
        expect($('#rating').data('barrating').deselectable).to.equal(true);
    });

    it('should successfully deselect rating', function () {
        expect($('#rating').data('barrating').currentRatingValue).to.equal('');
        expect($('#rating').data('barrating').currentRatingText).to.equal('');
    });

});

describe('bar rating plugin on clear', function () {

    before(function () {
        createSelect();

        $('#rating').barrating('show');
        $('.br-widget a:nth-child(6)').trigger('click');
        $('#rating').barrating('clear');
    });

    after(function () {
        $('#rating').barrating('destroy');
        destroySelect();
    });

    it('should restore original rating', function () {
        expect($('#rating').data('barrating').currentRatingValue).to.equal('5');
        expect($('#rating').data('barrating').currentRatingText).to.equal('rating-text-5');
    });

    it('should set correct class', function () {
        expect($('.br-widget a:nth-child(4)').hasClass('br-selected')).to.equal(true);
        expect($('.br-widget a:nth-child(5)').hasClass('br-selected br-current')).to.equal(true);
        expect($('.br-widget a:nth-child(6)').hasClass('br-selected')).to.equal(false);
    });
});

describe('bar rating plugin on destroy', function () {

    before(function () {
        createSelect();
        $('#rating')
            .barrating()
            .barrating('destroy');
    });

    after(function () {
        destroySelect();
    });

    it('should remove data', function () {
        expect($('#rating').data('barrating')).to.equal('');
    });

    it('should show the select field back again', function () {
        expect($('#rating').is(":visible")).to.equal(true);
    });

});

describe('bar rating plugin on set value', function () {

    before(function () {
        createSelect();
        $('#rating')
            .barrating()
            .barrating('set', 3);
    });

    after(function () {
        destroySelect();
    });

    it('should set correct value', function () {
        expect($('#rating').data('barrating').currentRatingValue).to.equal(3);
        expect($('#rating').data('barrating').currentRatingText).to.equal('rating-text-3');
    });

    it('should set correct class', function () {
        expect($('.br-widget a:nth-child(3)').hasClass('br-selected')).to.equal(true);
        expect($('.br-widget a:nth-child(3)').hasClass('br-current')).to.equal(true);
    });

});

describe('bar rating plugin on set non-existing value', function () {

    before(function () {
        createSelect();
        $('#rating')
            .barrating('show', { initialRating: 5 })
            .barrating('set', 9999);
    });

    after(function () {
        destroySelect();
    });

    it('should do nothing', function () {
        expect($('#rating').data('barrating').currentRatingValue).to.equal('5');
        expect($('#rating').data('barrating').currentRatingText).to.equal('rating-text-5');
    });

});
<commit_msg>tests for onDestroy/onClear callbacks<commit_after>var chai = require("chai"),
    jQuery = require("jQuery");

var expect = chai.expect;

var $ = global.jQuery = jQuery;

function createSelect() {
    $('<select />', { 'id':'rating', 'name':'rating' }).appendTo('body');

    for (var i = 1; i <= 10; i++) {
        var attributes = (i == 5) ?
            attributes = { 'value':i, 'selected':'selected' } :
            attributes = { 'value':i };

        $('<option />', attributes).appendTo('#rating').html('rating-text-'+i);
    }
}

function destroySelect() {
    $('#rating').remove();
}

require("../jquery.barrating");

describe('bar rating plugin on init with custom options', function () {

    it('should update defaults', function () {
        var BarRating;
        BarRating = new global.BarRating();
        BarRating.init({
            showValues: false
        });
        expect(BarRating.options).to.be.a('object');
        expect(BarRating.options.initialRating).to.equal(null);
        expect(BarRating.options.showValues).to.equal(false);
        expect(BarRating.options.showSelectedRating).to.equal(true);
        expect(BarRating.options.reverse).to.equal(false);
        expect(BarRating.options.readonly).to.equal(false);
    });

});

describe('bar rating plugin on show', function () {

    before(function () {
        createSelect();
        $('#rating').barrating('show');
    });

    after(function () {
        $('#rating').barrating('destroy');
        destroySelect();
    });

    it('should have data', function () {
        expect($('#rating').data('barrating')).to.be.a('object');
    });

    it('should transform the select field into a rating widget', function () {
        expect($('.br-widget a')).to.have.length(10);
    });

    it('should store rating values in data attributes', function () {
        expect($('.br-widget a:first').attr('data-rating-value')).to.equal('1');
        expect($('.br-widget a:nth-child(8)').attr('data-rating-value')).to.equal('8');
        expect($('.br-widget a:first').attr('data-rating-text')).to.equal('rating-text-1');
        expect($('.br-widget a:nth-child(8)').attr('data-rating-text')).to.equal('rating-text-8');
    });

    it('should read the selected rating from the select field', function () {
        expect($('#rating').data('barrating').currentRatingValue).to.equal('5');
        expect($('#rating').data('barrating').currentRatingText).to.equal('rating-text-5');
    });

    it('should set correct class', function () {
        expect($('.br-widget a:nth-child(4)').hasClass('br-selected')).to.equal(true);
        expect($('.br-widget a:nth-child(5)').hasClass('br-selected br-current')).to.equal(true);
        expect($('.br-widget a:nth-child(6)').hasClass('br-selected')).to.equal(false);
    });

    it('should append a rating div', function () {
        expect($('div.br-current-rating')).to.have.length(1);
    });

    it('should display a correct rating', function () {
        expect($('div.br-current-rating').html()).to.equal(
            $('#rating').data('barrating').currentRatingText
        );
    });

    it('should hide the select field', function () {
        expect($('#rating').is(":visible")).to.equal(false);
    });

});

describe('bar rating plugin on show and rating selected', function () {

    var valuesFromCallback = [];

    before(function () {
        createSelect();

        $('#rating').barrating('show', {
            onSelect:function (value, text) {
                valuesFromCallback.push(value, text);
            }
        });

        $('.br-widget a:nth-child(2)').trigger('click');
    });

    after(function () {
        $('#rating').barrating('destroy');
        destroySelect();
    });

    it('should update data', function () {
        expect($('#rating').data('barrating').currentRatingValue).to.equal('2');
        expect($('#rating').data('barrating').currentRatingText).to.equal('rating-text-2');
    });

    it('should set correct class', function () {
        expect($('.br-widget a:nth-child(1)').hasClass('br-selected')).to.equal(true);
        expect($('.br-widget a:nth-child(2)').hasClass('br-selected br-current')).to.equal(true);
        expect($('.br-widget a:nth-child(3)').hasClass('br-selected')).to.equal(false);
    });

    it('should display a correct rating', function () {
        expect($('div.br-current-rating').html()).to.equal(
            $('#rating').data('barrating').currentRatingText
        );
    });

    it('should pass correct values to a callback', function () {
        expect(valuesFromCallback[0]).to.equal('2');
        expect(valuesFromCallback[1]).to.equal('rating-text-2');
    });

});

describe('bar rating plugin reversed', function () {

    before(function () {
        createSelect();

        $('#rating').barrating('show', {
            reverse:true
        });
    });

    after(function () {
        $('#rating').barrating('destroy');
        destroySelect();
    });

    it('should set correct class', function () {
        expect($('.br-widget a:nth-child(4)').hasClass('br-selected')).to.equal(false);
        expect($('.br-widget a:nth-child(5)').hasClass('br-selected br-current')).to.equal(true);
        expect($('.br-widget a:nth-child(6)').hasClass('br-selected')).to.equal(true);
    });

});

describe('bar rating plugin read-only', function () {

    before(function () {
        createSelect();

        $('#rating').barrating('show', {
            readonly:true
        });

        $('.br-widget a:nth-child(6)').trigger('click');
    });

    after(function () {
        $('#rating').barrating('destroy');
        destroySelect();
    });

    it('should set correct class', function () {
        expect($('.br-widget a:nth-child(4)').hasClass('br-selected')).to.equal(true);
        expect($('.br-widget a:nth-child(5)').hasClass('br-selected br-current')).to.equal(true);
        expect($('.br-widget a:nth-child(6)').hasClass('br-selected')).to.equal(false);
    });

    it('should ignore user input', function () {
        expect($('#rating').data('barrating').currentRatingValue).to.equal('5');
        expect($('#rating').data('barrating').currentRatingText).to.equal('rating-text-5');
    });

});

describe('bar rating plugin on deselect', function () {

    before(function () {
        createSelect();

        // prepend empty OPTION to test deselectable ratings
        $('#rating').prepend($('<option />', { 'value':'' }));
        $('#rating').barrating('show');

        // deselect rating
        $('.br-widget a:nth-child(5)').trigger('click');
    });

    after(function () {
        $('#rating').barrating('destroy');
        destroySelect();
    });

    it('should update data', function () {
        expect($('#rating').data('barrating').deselectable).to.equal(true);
    });

    it('should successfully deselect rating', function () {
        expect($('#rating').data('barrating').currentRatingValue).to.equal('');
        expect($('#rating').data('barrating').currentRatingText).to.equal('');
    });

});

describe('bar rating plugin on clear', function () {

    var valuesFromCallback = [];

    before(function () {
        createSelect();

        $('#rating').barrating('show', {
            onClear:function (value, text) {
                valuesFromCallback.push(value, text);
            }
        });
        $('.br-widget a:nth-child(6)').trigger('click');
        $('#rating').barrating('clear');
    });

    after(function () {
        $('#rating').barrating('destroy');
        destroySelect();
    });

    it('should restore original rating', function () {
        expect($('#rating').data('barrating').currentRatingValue).to.equal('5');
        expect($('#rating').data('barrating').currentRatingText).to.equal('rating-text-5');
    });

    it('should set correct class', function () {
        expect($('.br-widget a:nth-child(4)').hasClass('br-selected')).to.equal(true);
        expect($('.br-widget a:nth-child(5)').hasClass('br-selected br-current')).to.equal(true);
        expect($('.br-widget a:nth-child(6)').hasClass('br-selected')).to.equal(false);
    });

    it('should pass correct values to a callback', function () {
        expect(valuesFromCallback[0]).to.equal('5');
        expect(valuesFromCallback[1]).to.equal('rating-text-5');
    });

});

describe('bar rating plugin on destroy', function () {

    var valuesFromCallback = [];

    before(function () {
        createSelect();

        $('#rating').barrating('show', {
            onDestroy:function (value, text) {
                valuesFromCallback.push(value, text);
            }
        });

        $('#rating').barrating('destroy');

    });

    after(function () {
        destroySelect();
    });

    it('should remove data', function () {
        expect($('#rating').data('barrating')).to.equal('');
    });

    it('should show the select field back again', function () {
        expect($('#rating').is(":visible")).to.equal(true);
    });

    it('should pass correct values to a callback', function () {
        expect(valuesFromCallback[0]).to.equal('5');
        expect(valuesFromCallback[1]).to.equal('rating-text-5');
    });

});

describe('bar rating plugin on set value', function () {

    before(function () {
        createSelect();
        $('#rating')
            .barrating()
            .barrating('set', 3);
    });

    after(function () {
        destroySelect();
    });

    it('should set correct value', function () {
        expect($('#rating').data('barrating').currentRatingValue).to.equal(3);
        expect($('#rating').data('barrating').currentRatingText).to.equal('rating-text-3');
    });

    it('should set correct class', function () {
        expect($('.br-widget a:nth-child(3)').hasClass('br-selected')).to.equal(true);
        expect($('.br-widget a:nth-child(3)').hasClass('br-current')).to.equal(true);
    });

});

describe('bar rating plugin on set non-existing value', function () {

    before(function () {
        createSelect();
        $('#rating')
            .barrating('show', { initialRating: 5 })
            .barrating('set', 9999);
    });

    after(function () {
        destroySelect();
    });

    it('should do nothing', function () {
        expect($('#rating').data('barrating').currentRatingValue).to.equal('5');
        expect($('#rating').data('barrating').currentRatingText).to.equal('rating-text-5');
    });

});
<|endoftext|>


<commit_before>import Backbone from 'backbone';
import { isEmpty, each, keys } from 'underscore';
import Components from '../model/Components';
import ComponentsView from './ComponentsView';
import Selectors from 'selector_manager/model/Selectors';
import { replaceWith } from 'utils/dom';
import { setViewEl } from 'utils/mixins';

export default Backbone.View.extend({
  className() {
    return this.getClasses();
  },

  tagName() {
    return this.model.get('tagName');
  },

  initialize(opt = {}) {
    const model = this.model;
    const config = opt.config || {};
    const em = config.em;
    const modelOpt = model.opt || {};
    const { $el, el } = this;
    const { draggableComponents } = config;
    this.opts = opt;
    this.modelOpt = modelOpt;
    this.config = config;
    this.em = em || '';
    this.pfx = config.stylePrefix || '';
    this.ppfx = config.pStylePrefix || '';
    this.attr = model.get('attributes');
    this.classe = this.attr.class || [];
    this.listenTo(model, 'change:style', this.updateStyle);
    this.listenTo(
      model,
      'change:attributes change:_innertext',
      this.renderAttributes
    );
    this.listenTo(model, 'change:highlightable', this.updateHighlight);
    this.listenTo(model, 'change:status', this.updateStatus);
    this.listenTo(model, 'change:script rerender', this.reset);
    this.listenTo(model, 'change:content', this.updateContent);
    this.listenTo(model, 'change', this.handleChange);
    this.listenTo(model, 'active', this.onActive);
    this.listenTo(model, 'disable', this.onDisable);
    $el.data('model', model);
    setViewEl(el, this);
    model.view = this;
    this._getFrame() && model.views.push(this);
    this.initClasses();
    this.initComponents({ avoidRender: 1 });
    this.events = {
      ...this.events,
      ...(this.__isDraggable() && { dragstart: 'handleDragStart' })
    };
    this.delegateEvents();
    !modelOpt.temporary && this.init(this._clbObj());
  },

  __isDraggable() {
    const { model, config } = this;
    const { _innertext, draggable } = model.attributes;
    return config.draggableComponents && draggable && !_innertext;
  },

  _clbObj() {
    const { em, model, el } = this;
    return {
      editor: em && em.getEditor(),
      model,
      el
    };
  },

  /**
   * Initialize callback
   */
  init() {},

  /**
   * Remove callback
   */
  removed() {},

  /**
   * Callback executed when the `active` event is triggered on component
   */
  onActive() {},

  /**
   * Callback executed when the `disable` event is triggered on component
   */
  onDisable() {},

  remove() {
    const view = this;
    Backbone.View.prototype.remove.apply(view, arguments);
    const { model } = view;
    const frame = view._getFrame() || {};
    const frameM = frame.model;
    model.components().forEach(comp => {
      const view = comp.getView(frameM);
      view && view.remove();
    });
    const cv = view.childrenView;
    cv && cv.remove();
    const { views } = model;
    views.splice(views.indexOf(view), 1);
    view.removed(view._clbObj());
    view.$el.data({ model: '', collection: '', view: '' });
    delete view.model;
    delete view.$el;
    delete view.el.__gjsv;
    delete view.childrenView;
    delete view.scriptContainer;
    delete view.opts;
    // delete view.el;
    return view;
  },

  handleDragStart(event) {
    event.preventDefault();
    event.stopPropagation();
    this.em.get('Commands').run('tlb-move', {
      target: this.model,
      event
    });
  },

  initClasses() {
    const { model } = this;
    const event = 'change:classes';
    const classes = model.get('classes');

    if (classes instanceof Selectors) {
      this.stopListening(model, event, this.initClasses);
      this.listenTo(model, event, this.initClasses);
      this.listenTo(classes, 'add remove change', this.updateClasses);
      classes.length && this.importClasses();
    }
  },

  initComponents(opts = {}) {
    const { model, $el, childrenView } = this;
    const event = 'change:components';
    const comps = model.get('components');
    const toListen = [model, event, this.initComponents];

    if (comps instanceof Components) {
      $el.data('collection', comps);
      childrenView && childrenView.remove();
      this.stopListening(...toListen);
      !opts.avoidRender && this.renderChildren();
      this.listenTo(...toListen);
    }
  },

  /**
   * Handle any property change
   * @private
   */
  handleChange() {
    const { model } = this;
    const chgArr = keys(model.changed);
    if (chgArr.length === 1 && chgArr[0] === 'status') return;
    model.emitUpdate();

    for (let prop in model.changed) {
      model.emitUpdate(prop);
    }
  },

  /**
   * Import, if possible, classes inside main container
   * @private
   * */
  importClasses() {
    var clm = this.config.em.get('SelectorManager');

    if (clm) {
      this.model.get('classes').each(m => {
        clm.add(m.get('name'));
      });
    }
  },

  /**
   * Update item on status change
   * @param  {Event} e
   * @private
   * */
  updateStatus(opts = {}) {
    const { em } = this;
    const { extHl } = em ? em.get('Canvas').getConfig() : {};
    const el = this.el;
    const status = this.model.get('status');
    const ppfx = this.ppfx;
    const selectedCls = `${ppfx}selected`;
    const selectedParentCls = `${selectedCls}-parent`;
    const freezedCls = `${ppfx}freezed`;
    const hoveredCls = `${ppfx}hovered`;
    const toRemove = [selectedCls, selectedParentCls, freezedCls, hoveredCls];
    const selCls = extHl && !opts.noExtHl ? '' : selectedCls;
    this.$el.removeClass(toRemove.join(' '));
    var actualCls = el.getAttribute('class') || '';
    var cls = '';

    switch (status) {
      case 'selected':
        cls = `${actualCls} ${selCls}`;
        break;
      case 'selected-parent':
        cls = `${actualCls} ${selectedParentCls}`;
        break;
      case 'freezed':
        cls = `${actualCls} ${freezedCls}`;
        break;
      case 'freezed-selected':
        cls = `${actualCls} ${freezedCls} ${selCls}`;
        break;
      case 'hovered':
        cls = !opts.avoidHover ? `${actualCls} ${hoveredCls}` : '';
        break;
    }

    cls = cls.trim();
    cls && el.setAttribute('class', cls);
  },

  /**
   * Update highlight attribute
   * @private
   * */
  updateHighlight() {
    const hl = this.model.get('highlightable');
    this.setAttribute('data-highlightable', hl ? 1 : '');
  },

  /**
   * Update style attribute
   * @private
   * */
  updateStyle() {
    const { model, em, el } = this;

    if (em && em.getConfig('avoidInlineStyle')) {
      const style = model.getStyle();
      const empty = isEmpty(style);
      !empty && model.setStyle(style);
      if (model.get('_innertext') && empty) {
        el.removeAttribute('id');
      } else {
        el.id = model.getId();
      }
    } else {
      this.setAttribute('style', model.styleToString());
    }
  },

  /**
   * Update classe attribute
   * @private
   * */
  updateClasses() {
    const str = this.model
      .get('classes')
      .pluck('name')
      .join(' ');
    this.setAttribute('class', str);

    // Regenerate status class
    this.updateStatus();
  },

  /**
   * Update single attribute
   * @param {[type]} name  [description]
   * @param {[type]} value [description]
   */
  setAttribute(name, value) {
    const el = this.$el;
    value ? el.attr(name, value) : el.removeAttr(name);
  },

  /**
   * Get classes from attributes.
   * This method is called before initialize
   *
   * @return  {Array}|null
   * @private
   * */
  getClasses() {
    return this.model.getClasses().join(' ');
  },

  /**
   * Update attributes
   * @private
   * */
  updateAttributes() {
    const attrs = [];
    const { model, $el, el, config } = this;
    const { highlightable, textable, type } = model.attributes;

    const defaultAttr = {
      'data-gjs-type': type || 'default',
      ...(this.__isDraggable() ? { draggable: true } : {}),
      ...(highlightable ? { 'data-highlightable': 1 } : {}),
      ...(textable
        ? {
            contenteditable: 'false',
            'data-gjs-textable': 'true'
          }
        : {})
    };

    // Remove all current attributes
    each(el.attributes, attr => attrs.push(attr.nodeName));
    attrs.forEach(attr => $el.removeAttr(attr));
    const attr = {
      ...defaultAttr,
      ...model.getAttributes()
    };

    // Remove all `false` attributes
    keys(attr).forEach(key => attr[key] === false && delete attr[key]);

    $el.attr(attr);
    this.updateStyle();
  },

  /**
   * Update component content
   * @private
   * */
  updateContent() {
    const content = this.model.get('content');
    const hasComps = this.model.components().length;
    this.getChildrenContainer().innerHTML = hasComps ? '' : content;
  },

  /**
   * Prevent default helper
   * @param  {Event} e
   * @private
   */
  prevDef(e) {
    e.preventDefault();
  },

  /**
   * Render component's script
   * @private
   */
  updateScript() {
    const { model, em } = this;
    if (!model.get('script')) return;
    em &&
      em
        .get('Canvas')
        .getCanvasView()
        .updateScript(this);
  },

  /**
   * Return children container
   * Differently from a simple component where children container is the
   * component itself
   * <my-comp>
   *
   * </my-comp>
   * You could have the children container more deeper
   * <my-comp>
   *

   *

   *

   *

   *
   *

   *

   * </my-comp>
   * @return HTMLElement
   * @private
   */
  getChildrenContainer() {
    var container = this.el;

    if (typeof this.getChildrenSelector == 'function') {
      container = this.el.querySelector(this.getChildrenSelector());
    } else if (typeof this.getTemplate == 'function') {
      // Need to find deepest first child
    }

    return container;
  },

  /**
   * This returns rect informations not affected by the canvas zoom.
   * The method `getBoundingClientRect` doesn't work here and we
   * have to take in account offsetParent
   */
  getOffsetRect() {
    const rect = {};
    const target = this.el;
    let gtop = 0;
    let gleft = 0;

    const assignRect = el => {
      const { offsetParent } = el;

      if (offsetParent) {
        gtop += offsetParent.offsetTop;
        gleft += offsetParent.offsetLeft;
        assignRect(offsetParent);
      } else {
        rect.top = target.offsetTop + gtop;
        rect.left = target.offsetLeft + gleft;
        rect.bottom = rect.top + target.offsetHeight;
        rect.right = rect.left + target.offsetWidth;
      }
    };
    assignRect(target);

    return rect;
  },

  isInViewport({ rect } = {}) {
    const { el } = this;
    const elDoc = el.ownerDocument;
    const { body } = elDoc;
    const { frameElement } = elDoc.defaultView;
    const { top, left } = rect || this.getOffsetRect();
    const frame = this._getFrame().getOffsetRect();

    return (
      top >= frame.scrollTop &&
      left >= frame.scrollLeft &&
      top <= frame.scrollBottom &&
      left <= frameElement.offsetWidth + body.scrollLeft
    );
  },

  scrollIntoView(opts = {}) {
    const rect = this.getOffsetRect();
    const isInViewport = this.isInViewport({ rect });

    if (!isInViewport || opts.force) {
      const { el } = this;

      // PATCH: scrollIntoView won't work with multiple requests from iframes
      if (opts.behavior !== 'smooth') {
        el.ownerDocument.defaultView.scrollTo(0, rect.top);
      } else {
        el.scrollIntoView({
          behavior: 'smooth',
          block: 'nearest',
          ...opts
        });
      }
    }
  },

  /**
   * Recreate the element of the view
   */
  reset() {
    const { el } = this;
    this.el = '';
    this._ensureElement();
    this._setData();
    replaceWith(el, this.el);
    this.render();
  },

  _setData() {
    const { model } = this;
    const collection = model.components();
    const view = this;
    this.$el.data({ model, collection, view });
  },

  _getFrame() {
    return this.config.frameView;
  },

  /**
   * Render children components
   * @private
   */
  renderChildren() {
    this.updateContent();
    const container = this.getChildrenContainer();
    const view =
      this.childrenView ||
      new ComponentsView({
        collection: this.model.get('components'),
        config: this.config,
        componentTypes: this.opts.componentTypes
      });

    view.render(container);
    this.childrenView = view;
    const childNodes = Array.prototype.slice.call(view.el.childNodes);

    for (var i = 0, len = childNodes.length; i < len; i++) {
      container.appendChild(childNodes.shift());
    }
  },

  renderAttributes() {
    this.updateAttributes();
    this.updateClasses();
    this.onAttrUpdate();
  },

  onAttrUpdate() {},

  render() {
    this.renderAttributes();
    if (this.modelOpt.temporary) return this;
    this.renderChildren();
    this.updateScript();
    setViewEl(this.el, this);
    this.postRender();

    return this;
  },

  postRender() {
    const { em, model, modelOpt } = this;

    if (!modelOpt.temporary) {
      this.onRender(this._clbObj());
      em && em.trigger('component:mount', model);
    }
  },

  onRender() {}
});
<commit_msg>Trigger attributes update in updateClasses<commit_after>import Backbone from 'backbone';
import { isEmpty, each, keys } from 'underscore';
import Components from '../model/Components';
import ComponentsView from './ComponentsView';
import Selectors from 'selector_manager/model/Selectors';
import { replaceWith } from 'utils/dom';
import { setViewEl } from 'utils/mixins';

export default Backbone.View.extend({
  className() {
    return this.getClasses();
  },

  tagName() {
    return this.model.get('tagName');
  },

  initialize(opt = {}) {
    const model = this.model;
    const config = opt.config || {};
    const em = config.em;
    const modelOpt = model.opt || {};
    const { $el, el } = this;
    const { draggableComponents } = config;
    this.opts = opt;
    this.modelOpt = modelOpt;
    this.config = config;
    this.em = em || '';
    this.pfx = config.stylePrefix || '';
    this.ppfx = config.pStylePrefix || '';
    this.attr = model.get('attributes');
    this.classe = this.attr.class || [];
    this.listenTo(model, 'change:style', this.updateStyle);
    this.listenTo(
      model,
      'change:attributes change:_innertext',
      this.renderAttributes
    );
    this.listenTo(model, 'change:highlightable', this.updateHighlight);
    this.listenTo(model, 'change:status', this.updateStatus);
    this.listenTo(model, 'change:script rerender', this.reset);
    this.listenTo(model, 'change:content', this.updateContent);
    this.listenTo(model, 'change', this.handleChange);
    this.listenTo(model, 'active', this.onActive);
    this.listenTo(model, 'disable', this.onDisable);
    $el.data('model', model);
    setViewEl(el, this);
    model.view = this;
    this._getFrame() && model.views.push(this);
    this.initClasses();
    this.initComponents({ avoidRender: 1 });
    this.events = {
      ...this.events,
      ...(this.__isDraggable() && { dragstart: 'handleDragStart' })
    };
    this.delegateEvents();
    !modelOpt.temporary && this.init(this._clbObj());
  },

  __isDraggable() {
    const { model, config } = this;
    const { _innertext, draggable } = model.attributes;
    return config.draggableComponents && draggable && !_innertext;
  },

  _clbObj() {
    const { em, model, el } = this;
    return {
      editor: em && em.getEditor(),
      model,
      el
    };
  },

  /**
   * Initialize callback
   */
  init() {},

  /**
   * Remove callback
   */
  removed() {},

  /**
   * Callback executed when the `active` event is triggered on component
   */
  onActive() {},

  /**
   * Callback executed when the `disable` event is triggered on component
   */
  onDisable() {},

  remove() {
    const view = this;
    Backbone.View.prototype.remove.apply(view, arguments);
    const { model } = view;
    const frame = view._getFrame() || {};
    const frameM = frame.model;
    model.components().forEach(comp => {
      const view = comp.getView(frameM);
      view && view.remove();
    });
    const cv = view.childrenView;
    cv && cv.remove();
    const { views } = model;
    views.splice(views.indexOf(view), 1);
    view.removed(view._clbObj());
    view.$el.data({ model: '', collection: '', view: '' });
    delete view.model;
    delete view.$el;
    delete view.el.__gjsv;
    delete view.childrenView;
    delete view.scriptContainer;
    delete view.opts;
    // delete view.el;
    return view;
  },

  handleDragStart(event) {
    event.preventDefault();
    event.stopPropagation();
    this.em.get('Commands').run('tlb-move', {
      target: this.model,
      event
    });
  },

  initClasses() {
    const { model } = this;
    const event = 'change:classes';
    const classes = model.get('classes');

    if (classes instanceof Selectors) {
      this.stopListening(model, event, this.initClasses);
      this.listenTo(model, event, this.initClasses);
      this.listenTo(classes, 'add remove change', this.updateClasses);
      classes.length && this.importClasses();
    }
  },

  initComponents(opts = {}) {
    const { model, $el, childrenView } = this;
    const event = 'change:components';
    const comps = model.get('components');
    const toListen = [model, event, this.initComponents];

    if (comps instanceof Components) {
      $el.data('collection', comps);
      childrenView && childrenView.remove();
      this.stopListening(...toListen);
      !opts.avoidRender && this.renderChildren();
      this.listenTo(...toListen);
    }
  },

  /**
   * Handle any property change
   * @private
   */
  handleChange() {
    const { model } = this;
    const chgArr = keys(model.changed);
    if (chgArr.length === 1 && chgArr[0] === 'status') return;
    model.emitUpdate();

    for (let prop in model.changed) {
      model.emitUpdate(prop);
    }
  },

  /**
   * Import, if possible, classes inside main container
   * @private
   * */
  importClasses() {
    var clm = this.config.em.get('SelectorManager');

    if (clm) {
      this.model.get('classes').each(m => {
        clm.add(m.get('name'));
      });
    }
  },

  /**
   * Update item on status change
   * @param  {Event} e
   * @private
   * */
  updateStatus(opts = {}) {
    const { em } = this;
    const { extHl } = em ? em.get('Canvas').getConfig() : {};
    const el = this.el;
    const status = this.model.get('status');
    const ppfx = this.ppfx;
    const selectedCls = `${ppfx}selected`;
    const selectedParentCls = `${selectedCls}-parent`;
    const freezedCls = `${ppfx}freezed`;
    const hoveredCls = `${ppfx}hovered`;
    const toRemove = [selectedCls, selectedParentCls, freezedCls, hoveredCls];
    const selCls = extHl && !opts.noExtHl ? '' : selectedCls;
    this.$el.removeClass(toRemove.join(' '));
    var actualCls = el.getAttribute('class') || '';
    var cls = '';

    switch (status) {
      case 'selected':
        cls = `${actualCls} ${selCls}`;
        break;
      case 'selected-parent':
        cls = `${actualCls} ${selectedParentCls}`;
        break;
      case 'freezed':
        cls = `${actualCls} ${freezedCls}`;
        break;
      case 'freezed-selected':
        cls = `${actualCls} ${freezedCls} ${selCls}`;
        break;
      case 'hovered':
        cls = !opts.avoidHover ? `${actualCls} ${hoveredCls}` : '';
        break;
    }

    cls = cls.trim();
    cls && el.setAttribute('class', cls);
  },

  /**
   * Update highlight attribute
   * @private
   * */
  updateHighlight() {
    const hl = this.model.get('highlightable');
    this.setAttribute('data-highlightable', hl ? 1 : '');
  },

  /**
   * Update style attribute
   * @private
   * */
  updateStyle() {
    const { model, em, el } = this;

    if (em && em.getConfig('avoidInlineStyle')) {
      const style = model.getStyle();
      const empty = isEmpty(style);
      !empty && model.setStyle(style);
      if (model.get('_innertext') && empty) {
        el.removeAttribute('id');
      } else {
        el.id = model.getId();
      }
    } else {
      this.setAttribute('style', model.styleToString());
    }
  },

  /**
   * Update classe attribute
   * @private
   * */
  updateClasses() {
    const str = this.model
      .get('classes')
      .pluck('name')
      .join(' ');
    this.setAttribute('class', str);

    // Regenerate status class
    this.updateStatus();
    this.onAttrUpdate();
  },

  /**
   * Update single attribute
   * @param {[type]} name  [description]
   * @param {[type]} value [description]
   */
  setAttribute(name, value) {
    const el = this.$el;
    value ? el.attr(name, value) : el.removeAttr(name);
  },

  /**
   * Get classes from attributes.
   * This method is called before initialize
   *
   * @return  {Array}|null
   * @private
   * */
  getClasses() {
    return this.model.getClasses().join(' ');
  },

  /**
   * Update attributes
   * @private
   * */
  updateAttributes() {
    const attrs = [];
    const { model, $el, el, config } = this;
    const { highlightable, textable, type } = model.attributes;

    const defaultAttr = {
      'data-gjs-type': type || 'default',
      ...(this.__isDraggable() ? { draggable: true } : {}),
      ...(highlightable ? { 'data-highlightable': 1 } : {}),
      ...(textable
        ? {
            contenteditable: 'false',
            'data-gjs-textable': 'true'
          }
        : {})
    };

    // Remove all current attributes
    each(el.attributes, attr => attrs.push(attr.nodeName));
    attrs.forEach(attr => $el.removeAttr(attr));
    const attr = {
      ...defaultAttr,
      ...model.getAttributes()
    };

    // Remove all `false` attributes
    keys(attr).forEach(key => attr[key] === false && delete attr[key]);

    $el.attr(attr);
    this.updateStyle();
  },

  /**
   * Update component content
   * @private
   * */
  updateContent() {
    const content = this.model.get('content');
    const hasComps = this.model.components().length;
    this.getChildrenContainer().innerHTML = hasComps ? '' : content;
  },

  /**
   * Prevent default helper
   * @param  {Event} e
   * @private
   */
  prevDef(e) {
    e.preventDefault();
  },

  /**
   * Render component's script
   * @private
   */
  updateScript() {
    const { model, em } = this;
    if (!model.get('script')) return;
    em &&
      em
        .get('Canvas')
        .getCanvasView()
        .updateScript(this);
  },

  /**
   * Return children container
   * Differently from a simple component where children container is the
   * component itself
   * <my-comp>
   *
   * </my-comp>
   * You could have the children container more deeper
   * <my-comp>
   *

   *

   *

   *

   *
   *

   *

   * </my-comp>
   * @return HTMLElement
   * @private
   */
  getChildrenContainer() {
    var container = this.el;

    if (typeof this.getChildrenSelector == 'function') {
      container = this.el.querySelector(this.getChildrenSelector());
    } else if (typeof this.getTemplate == 'function') {
      // Need to find deepest first child
    }

    return container;
  },

  /**
   * This returns rect informations not affected by the canvas zoom.
   * The method `getBoundingClientRect` doesn't work here and we
   * have to take in account offsetParent
   */
  getOffsetRect() {
    const rect = {};
    const target = this.el;
    let gtop = 0;
    let gleft = 0;

    const assignRect = el => {
      const { offsetParent } = el;

      if (offsetParent) {
        gtop += offsetParent.offsetTop;
        gleft += offsetParent.offsetLeft;
        assignRect(offsetParent);
      } else {
        rect.top = target.offsetTop + gtop;
        rect.left = target.offsetLeft + gleft;
        rect.bottom = rect.top + target.offsetHeight;
        rect.right = rect.left + target.offsetWidth;
      }
    };
    assignRect(target);

    return rect;
  },

  isInViewport({ rect } = {}) {
    const { el } = this;
    const elDoc = el.ownerDocument;
    const { body } = elDoc;
    const { frameElement } = elDoc.defaultView;
    const { top, left } = rect || this.getOffsetRect();
    const frame = this._getFrame().getOffsetRect();

    return (
      top >= frame.scrollTop &&
      left >= frame.scrollLeft &&
      top <= frame.scrollBottom &&
      left <= frameElement.offsetWidth + body.scrollLeft
    );
  },

  scrollIntoView(opts = {}) {
    const rect = this.getOffsetRect();
    const isInViewport = this.isInViewport({ rect });

    if (!isInViewport || opts.force) {
      const { el } = this;

      // PATCH: scrollIntoView won't work with multiple requests from iframes
      if (opts.behavior !== 'smooth') {
        el.ownerDocument.defaultView.scrollTo(0, rect.top);
      } else {
        el.scrollIntoView({
          behavior: 'smooth',
          block: 'nearest',
          ...opts
        });
      }
    }
  },

  /**
   * Recreate the element of the view
   */
  reset() {
    const { el } = this;
    this.el = '';
    this._ensureElement();
    this._setData();
    replaceWith(el, this.el);
    this.render();
  },

  _setData() {
    const { model } = this;
    const collection = model.components();
    const view = this;
    this.$el.data({ model, collection, view });
  },

  _getFrame() {
    return this.config.frameView;
  },

  /**
   * Render children components
   * @private
   */
  renderChildren() {
    this.updateContent();
    const container = this.getChildrenContainer();
    const view =
      this.childrenView ||
      new ComponentsView({
        collection: this.model.get('components'),
        config: this.config,
        componentTypes: this.opts.componentTypes
      });

    view.render(container);
    this.childrenView = view;
    const childNodes = Array.prototype.slice.call(view.el.childNodes);

    for (var i = 0, len = childNodes.length; i < len; i++) {
      container.appendChild(childNodes.shift());
    }
  },

  renderAttributes() {
    this.updateAttributes();
    this.updateClasses();
  },

  onAttrUpdate() {},

  render() {
    this.renderAttributes();
    if (this.modelOpt.temporary) return this;
    this.renderChildren();
    this.updateScript();
    setViewEl(this.el, this);
    this.postRender();

    return this;
  },

  postRender() {
    const { em, model, modelOpt } = this;

    if (!modelOpt.temporary) {
      this.onRender(this._clbObj());
      em && em.trigger('component:mount', model);
    }
  },

  onRender() {}
});
<|endoftext|>


<commit_before>var request = require('request');

var UKPD = function() {

  const baseURL = 'https://data.police.uk/api';

  function makeRequest (path) {
    return new Promise(function(resolve, reject) {
      request(`${baseURL}${path}`, function(error, response, body) {
        if (error) { return reject(error); }
        if (response.statusCode !== 200) { return reject({message: 'Non-200 status code received: ${response.statusCode}'}); }
        var data;
        try {
          data = JSON.parse(body);
        } catch (e) {
          return reject(e);
        }
        return resolve(data);
      });
    });
  };

  return {
    streetLevel: makeRequest('/crimes-street/all-crime?lat=52.629729&lng=-1.131592')
  };

};
<commit_msg>Export the thing<commit_after>var request = require('request');

var UKPD = function() {

  const baseURL = 'https://data.police.uk/api';

  function makeRequest (path) {
    return new Promise(function(resolve, reject) {
      request(`${baseURL}${path}`, function(error, response, body) {
        if (error) { return reject(error); }
        if (response.statusCode !== 200) { return reject({message: 'Non-200 status code received: ${response.statusCode}'}); }
        var data;
        try {
          data = JSON.parse(body);
        } catch (e) {
          return reject(e);
        }
        return resolve(data);
      });
    });
  };

  return {
    streetLevel: makeRequest('/crimes-street/all-crime?lat=52.629729&lng=-1.131592')
  };

};

module.exports = new UKPD();
<|endoftext|>


<commit_before>/* global appendChartID, coordsFromTranslate, loadDateFixture */
describe('dc.legend', function () {
    var id, chart, dateDimension, dateValueSumGroup, dateIdSumGroup;

    beforeEach(function () {
        var data = crossfilter(loadDateFixture());
        dateDimension = data.dimension(function (d) { return d3.time.day.utc(d.dd); });
        dateValueSumGroup = dateDimension.group().reduceSum(function (d) { return d.value; });
        dateIdSumGroup = dateDimension.group().reduceSum(function (d) { return d.id; });

        id = 'legend-chart';
        appendChartID(id);
        chart = dc.lineChart('#' + id);

        chart
            .dimension(dateDimension)
            .group(dateIdSumGroup, 'Id Sum')
            .stack(dateValueSumGroup, 'Value Sum')
            .stack(dateValueSumGroup, 'Fixed', function () {})
            .x(d3.time.scale.utc().domain([new Date(2012, 4, 20), new Date(2012, 7, 15)]))
            .legend(dc.legend().x(400).y(10).itemHeight(13).gap(5));
    });

    describe('rendering the legend', function () {
        beforeEach(function () {
            chart.render();
        });

        it('should generate a legend', function () {
            expect(chart.select('g.dc-legend').empty()).toBeFalsy();
        });

        it('should place the legend using the provided x and y values', function () {
            expect(chart.select('g.dc-legend').attr('transform')).toMatchTranslate(400,10);
        });

        it('should generate a legend item for each stacked line', function () {
            expect(chart.select('g.dc-legend').selectAll('g.dc-legend-item').size()).toBe(3);
        });

        describe('without .horizontal(true)', function () {
            it('should place legend items vertically', function () {
                expect(coordsFromTranslate(legendItem(0).attr('transform')).y).toBeWithinDelta(0, 1);
                expect(coordsFromTranslate(legendItem(1).attr('transform')).y).toBeWithinDelta(18, 2);
                expect(coordsFromTranslate(legendItem(2).attr('transform')).y).toBeWithinDelta(36, 4);
            });
        });

        describe('with .horizontal(true)', function () {
            beforeEach(function () {
                chart.legend(dc.legend().horizontal(true));
                chart.render();
            });

            it('should place legend items horizontally', function () {
                expect(coordsFromTranslate(legendItem(0).attr('transform')).x).toBeWithinDelta(0, 1);
                expect(coordsFromTranslate(legendItem(1).attr('transform')).x).toBeWithinDelta(65, 5);
                expect(coordsFromTranslate(legendItem(2).attr('transform')).x).toBeWithinDelta(155, 15);
            });
        });

        describe('with .horizontal(true) and defined legendWidth and itemWidth', function () {
            beforeEach(function () {
                chart.legend(dc.legend().horizontal(true).legendWidth(60).itemWidth(30));
                chart.render();
            });

            it('should place legend items in two columns. third item is new row', function () {
                expect(coordsFromTranslate(legendItem(0).attr('transform')).x).toBeWithinDelta(0, 1);
                expect(coordsFromTranslate(legendItem(1).attr('transform')).x).toBeWithinDelta(30, 5);
                expect(coordsFromTranslate(legendItem(2).attr('transform')).x).toBeWithinDelta(0, 1);
                expect(coordsFromTranslate(legendItem(0).attr('transform')).y).toBeWithinDelta(0, 1);
                expect(coordsFromTranslate(legendItem(1).attr('transform')).y).toBeWithinDelta(0, 1);
                expect(coordsFromTranslate(legendItem(2).attr('transform')).y).toBeWithinDelta(13, 5);
            });
        });

        describe('with .autoItemWidth not called', function () {
            beforeEach(function () {
                chart.legend(dc.legend());
            });

            it('_autoItemWidth should be false', function () {
                expect(chart.legend().autoItemWidth()).toBe(false);
            });
        });

        describe('with .autoItemWidth(false)', function () {
            beforeEach(function () {
                chart.legend(dc.legend().autoItemWidth(false));
            });

            it('_autoItemWidth should be false', function () {
                expect(chart.legend().autoItemWidth()).toBe(false);
            });
        });

        describe('with .autoItemWidth(true)', function () {
            beforeEach(function () {
                chart.legend(dc.legend().autoItemWidth(true));
            });
            it('_autoItemWidth should be true', function () {
                expect(chart.legend().autoItemWidth()).toBe(true);
            });
        });

        describe('with .horizontal(true) and .autoItemWidth(true)', function () {

            var
                autoWidthOffset1, fixedWidthOffset1,
                autoWidthOffset2, fixedWidthOffset2;

            beforeEach(function () {
                chart.legend(dc.legend().horizontal(true).itemWidth(30).autoItemWidth(false));
                chart.render();
                fixedWidthOffset1 = coordsFromTranslate(legendItem(1).attr('transform')).x;
                fixedWidthOffset2 = coordsFromTranslate(legendItem(2).attr('transform')).x;
                chart.legend(dc.legend().horizontal(true).itemWidth(30).autoItemWidth(true));
                chart.render();
                autoWidthOffset1  = coordsFromTranslate(legendItem(1).attr('transform')).x;
                autoWidthOffset2  = coordsFromTranslate(legendItem(2).attr('transform')).x;
            });

            it('autoWidth x offset should be greater than fixedWidth x offset for some legend items', function () {
                expect(autoWidthOffset1).toBeGreaterThan(fixedWidthOffset1);
                expect(autoWidthOffset2).toBeGreaterThan(fixedWidthOffset2);
            });
        });

        it('should generate legend item boxes', function () {
            expect(legendIcon(0).attr('width')).toBeWithinDelta(13,2);
            expect(legendIcon(0).attr('height')).toBeWithinDelta(13, 2);
        });

        it('should color the legend item boxes using the chart line colors', function () {
            expect(legendIcon(0).attr('fill')).toBe('#1f77b4');
            expect(legendIcon(1).attr('fill')).toBe('#ff7f0e');
            expect(legendIcon(2).attr('fill')).toBe('#2ca02c');
        });

        it('should generate a legend label for each chart line', function () {
            expect(chart.selectAll('g.dc-legend g.dc-legend-item text').size()).toBe(3);
        });

        it('should position the legend labels', function () {
            expect(legendLabel(0).attr('x')).toBeWithinDelta(15, 2);
            expect(legendLabel(0).attr('y')).toBeWithinDelta(13, 2);
            expect(legendLabel(1).attr('x')).toBeWithinDelta(15, 2);
            expect(legendLabel(1).attr('y')).toBeWithinDelta(13, 2);
            expect(legendLabel(2).attr('x')).toBeWithinDelta(15, 2);
            expect(legendLabel(2).attr('y')).toBeWithinDelta(13, 2);
        });

        it('should label the legend items with the names of their associated stacks', function () {
            expect(legendLabel(0).text()).toBe('Id Sum');
            expect(legendLabel(1).text()).toBe('Value Sum');
            expect(legendLabel(2).text()).toBe('Fixed');
        });

        it('not allow hiding stacks be default', function () {
            legendItem(0).on('click').call(legendItem(0)[0][0], legendItem(0).datum());
            expect(chart.selectAll('path.line').size()).toBe(3);
        });
    });

    describe('legends with dashed lines', function () {
        beforeEach(function () {
            id = 'legend-chart-dashed';
            appendChartID(id);
            chart = dc.compositeChart('#' + id);

            var subChart1 = dc.lineChart();
            subChart1
                .dimension(dateDimension)
                .group(dateIdSumGroup, 'Id Sum')
                .dashStyle([10,1]);

            var subChart2 = dc.lineChart();
            subChart2
                .dimension(dateDimension)
                .group(dateValueSumGroup, 'Value Sum')
                .dashStyle([2,1]);

            chart
                .x(d3.scale.linear().domain([0,20]))
                .legend(dc.legend().x(400).y(10).itemHeight(13).gap(5))
                .compose([subChart1, subChart2])
                .render();
        });

        it('should style legend line correctly', function () {
            expect(legendLine(0).attr('stroke-dasharray')).toEqual('10,1');
            expect(legendLine(1).attr('stroke-dasharray')).toEqual('2,1');
        });
    });

    describe('legends with hidable stacks', function () {
        beforeEach(function () {
            chart.hidableStacks(true).render();
        });

        describe('clicking on a legend item', function () {
            beforeEach(function () {
                legendItem(0).on('click').call(legendItem(0)[0][0], legendItem(0).datum());
            });

            it('should fade out the legend item', function () {
                expect(legendItem(0).classed('fadeout')).toBeTruthy();
            });

            it('should hide its associated stack', function () {
                expect(chart.selectAll('path.line').size()).toEqual(2);
            });

            it('disable hover highlighting for that legend item', function () {
                legendItem(0).on('mouseover')(legendItem(0).datum());
                expect(d3.select(chart.selectAll('path.line')[0][1]).classed('fadeout')).toBeFalsy();
            });

            describe('clicking on a faded out legend item', function () {
                beforeEach(function () {
                    legendItem(0).on('click').call(legendItem(0)[0][0], legendItem(0).datum());
                });

                it('should unfade the legend item', function () {
                    expect(legendItem(0).classed('fadeout')).toBeFalsy();
                });

                it('should unfade its associated stack', function () {
                    expect(chart.selectAll('path.line').size()).toEqual(3);
                });
            });
        });
    });

    function legendItem (n) {
        return d3.select(chart.selectAll('g.dc-legend g.dc-legend-item')[0][n]);
    }
    function legendLabel (n) {
        return d3.select(chart.selectAll('g.dc-legend g.dc-legend-item text')[0][n]);
    }
    function legendIcon (n) {
        return d3.select(chart.selectAll('g.dc-legend g.dc-legend-item rect')[0][n]);
    }
    function legendLine (n) {
        return d3.select(chart.selectAll('g.dc-legend g.dc-legend-item line')[0][n]);
    }
});

<commit_msg>specify mandatory parent in legend test<commit_after>/* global appendChartID, coordsFromTranslate, loadDateFixture */
describe('dc.legend', function () {
    var id, chart, dateDimension, dateValueSumGroup, dateIdSumGroup;

    beforeEach(function () {
        var data = crossfilter(loadDateFixture());
        dateDimension = data.dimension(function (d) { return d3.time.day.utc(d.dd); });
        dateValueSumGroup = dateDimension.group().reduceSum(function (d) { return d.value; });
        dateIdSumGroup = dateDimension.group().reduceSum(function (d) { return d.id; });

        id = 'legend-chart';
        appendChartID(id);
        chart = dc.lineChart('#' + id);

        chart
            .dimension(dateDimension)
            .group(dateIdSumGroup, 'Id Sum')
            .stack(dateValueSumGroup, 'Value Sum')
            .stack(dateValueSumGroup, 'Fixed', function () {})
            .x(d3.time.scale.utc().domain([new Date(2012, 4, 20), new Date(2012, 7, 15)]))
            .legend(dc.legend().x(400).y(10).itemHeight(13).gap(5));
    });

    describe('rendering the legend', function () {
        beforeEach(function () {
            chart.render();
        });

        it('should generate a legend', function () {
            expect(chart.select('g.dc-legend').empty()).toBeFalsy();
        });

        it('should place the legend using the provided x and y values', function () {
            expect(chart.select('g.dc-legend').attr('transform')).toMatchTranslate(400,10);
        });

        it('should generate a legend item for each stacked line', function () {
            expect(chart.select('g.dc-legend').selectAll('g.dc-legend-item').size()).toBe(3);
        });

        describe('without .horizontal(true)', function () {
            it('should place legend items vertically', function () {
                expect(coordsFromTranslate(legendItem(0).attr('transform')).y).toBeWithinDelta(0, 1);
                expect(coordsFromTranslate(legendItem(1).attr('transform')).y).toBeWithinDelta(18, 2);
                expect(coordsFromTranslate(legendItem(2).attr('transform')).y).toBeWithinDelta(36, 4);
            });
        });

        describe('with .horizontal(true)', function () {
            beforeEach(function () {
                chart.legend(dc.legend().horizontal(true));
                chart.render();
            });

            it('should place legend items horizontally', function () {
                expect(coordsFromTranslate(legendItem(0).attr('transform')).x).toBeWithinDelta(0, 1);
                expect(coordsFromTranslate(legendItem(1).attr('transform')).x).toBeWithinDelta(65, 5);
                expect(coordsFromTranslate(legendItem(2).attr('transform')).x).toBeWithinDelta(155, 15);
            });
        });

        describe('with .horizontal(true) and defined legendWidth and itemWidth', function () {
            beforeEach(function () {
                chart.legend(dc.legend().horizontal(true).legendWidth(60).itemWidth(30));
                chart.render();
            });

            it('should place legend items in two columns. third item is new row', function () {
                expect(coordsFromTranslate(legendItem(0).attr('transform')).x).toBeWithinDelta(0, 1);
                expect(coordsFromTranslate(legendItem(1).attr('transform')).x).toBeWithinDelta(30, 5);
                expect(coordsFromTranslate(legendItem(2).attr('transform')).x).toBeWithinDelta(0, 1);
                expect(coordsFromTranslate(legendItem(0).attr('transform')).y).toBeWithinDelta(0, 1);
                expect(coordsFromTranslate(legendItem(1).attr('transform')).y).toBeWithinDelta(0, 1);
                expect(coordsFromTranslate(legendItem(2).attr('transform')).y).toBeWithinDelta(13, 5);
            });
        });

        describe('with .autoItemWidth not called', function () {
            beforeEach(function () {
                chart.legend(dc.legend());
            });

            it('_autoItemWidth should be false', function () {
                expect(chart.legend().autoItemWidth()).toBe(false);
            });
        });

        describe('with .autoItemWidth(false)', function () {
            beforeEach(function () {
                chart.legend(dc.legend().autoItemWidth(false));
            });

            it('_autoItemWidth should be false', function () {
                expect(chart.legend().autoItemWidth()).toBe(false);
            });
        });

        describe('with .autoItemWidth(true)', function () {
            beforeEach(function () {
                chart.legend(dc.legend().autoItemWidth(true));
            });
            it('_autoItemWidth should be true', function () {
                expect(chart.legend().autoItemWidth()).toBe(true);
            });
        });

        describe('with .horizontal(true) and .autoItemWidth(true)', function () {

            var
                autoWidthOffset1, fixedWidthOffset1,
                autoWidthOffset2, fixedWidthOffset2;

            beforeEach(function () {
                chart.legend(dc.legend().horizontal(true).itemWidth(30).autoItemWidth(false));
                chart.render();
                fixedWidthOffset1 = coordsFromTranslate(legendItem(1).attr('transform')).x;
                fixedWidthOffset2 = coordsFromTranslate(legendItem(2).attr('transform')).x;
                chart.legend(dc.legend().horizontal(true).itemWidth(30).autoItemWidth(true));
                chart.render();
                autoWidthOffset1  = coordsFromTranslate(legendItem(1).attr('transform')).x;
                autoWidthOffset2  = coordsFromTranslate(legendItem(2).attr('transform')).x;
            });

            it('autoWidth x offset should be greater than fixedWidth x offset for some legend items', function () {
                expect(autoWidthOffset1).toBeGreaterThan(fixedWidthOffset1);
                expect(autoWidthOffset2).toBeGreaterThan(fixedWidthOffset2);
            });
        });

        it('should generate legend item boxes', function () {
            expect(legendIcon(0).attr('width')).toBeWithinDelta(13,2);
            expect(legendIcon(0).attr('height')).toBeWithinDelta(13, 2);
        });

        it('should color the legend item boxes using the chart line colors', function () {
            expect(legendIcon(0).attr('fill')).toBe('#1f77b4');
            expect(legendIcon(1).attr('fill')).toBe('#ff7f0e');
            expect(legendIcon(2).attr('fill')).toBe('#2ca02c');
        });

        it('should generate a legend label for each chart line', function () {
            expect(chart.selectAll('g.dc-legend g.dc-legend-item text').size()).toBe(3);
        });

        it('should position the legend labels', function () {
            expect(legendLabel(0).attr('x')).toBeWithinDelta(15, 2);
            expect(legendLabel(0).attr('y')).toBeWithinDelta(13, 2);
            expect(legendLabel(1).attr('x')).toBeWithinDelta(15, 2);
            expect(legendLabel(1).attr('y')).toBeWithinDelta(13, 2);
            expect(legendLabel(2).attr('x')).toBeWithinDelta(15, 2);
            expect(legendLabel(2).attr('y')).toBeWithinDelta(13, 2);
        });

        it('should label the legend items with the names of their associated stacks', function () {
            expect(legendLabel(0).text()).toBe('Id Sum');
            expect(legendLabel(1).text()).toBe('Value Sum');
            expect(legendLabel(2).text()).toBe('Fixed');
        });

        it('not allow hiding stacks be default', function () {
            legendItem(0).on('click').call(legendItem(0)[0][0], legendItem(0).datum());
            expect(chart.selectAll('path.line').size()).toBe(3);
        });
    });

    describe('legends with dashed lines', function () {
        beforeEach(function () {
            id = 'legend-chart-dashed';
            appendChartID(id);
            chart = dc.compositeChart('#' + id);

            var subChart1 = dc.lineChart(chart);
            subChart1
                .dimension(dateDimension)
                .group(dateIdSumGroup, 'Id Sum')
                .dashStyle([10,1]);

            var subChart2 = dc.lineChart(chart);
            subChart2
                .dimension(dateDimension)
                .group(dateValueSumGroup, 'Value Sum')
                .dashStyle([2,1]);

            chart
                .x(d3.scale.linear().domain([0,20]))
                .legend(dc.legend().x(400).y(10).itemHeight(13).gap(5))
                .compose([subChart1, subChart2])
                .render();
        });

        it('should style legend line correctly', function () {
            expect(legendLine(0).attr('stroke-dasharray')).toEqual('10,1');
            expect(legendLine(1).attr('stroke-dasharray')).toEqual('2,1');
        });
    });

    describe('legends with hidable stacks', function () {
        beforeEach(function () {
            chart.hidableStacks(true).render();
        });

        describe('clicking on a legend item', function () {
            beforeEach(function () {
                legendItem(0).on('click').call(legendItem(0)[0][0], legendItem(0).datum());
            });

            it('should fade out the legend item', function () {
                expect(legendItem(0).classed('fadeout')).toBeTruthy();
            });

            it('should hide its associated stack', function () {
                expect(chart.selectAll('path.line').size()).toEqual(2);
            });

            it('disable hover highlighting for that legend item', function () {
                legendItem(0).on('mouseover')(legendItem(0).datum());
                expect(d3.select(chart.selectAll('path.line')[0][1]).classed('fadeout')).toBeFalsy();
            });

            describe('clicking on a faded out legend item', function () {
                beforeEach(function () {
                    legendItem(0).on('click').call(legendItem(0)[0][0], legendItem(0).datum());
                });

                it('should unfade the legend item', function () {
                    expect(legendItem(0).classed('fadeout')).toBeFalsy();
                });

                it('should unfade its associated stack', function () {
                    expect(chart.selectAll('path.line').size()).toEqual(3);
                });
            });
        });
    });

    function legendItem (n) {
        return d3.select(chart.selectAll('g.dc-legend g.dc-legend-item')[0][n]);
    }
    function legendLabel (n) {
        return d3.select(chart.selectAll('g.dc-legend g.dc-legend-item text')[0][n]);
    }
    function legendIcon (n) {
        return d3.select(chart.selectAll('g.dc-legend g.dc-legend-item rect')[0][n]);
    }
    function legendLine (n) {
        return d3.select(chart.selectAll('g.dc-legend g.dc-legend-item line')[0][n]);
    }
});

<|endoftext|>