<commit_before>(function (window, $, ch) {
    'use strict';

    if (ch === undefined) {
        throw new window.Error('Expected ch namespace defined.');
    }

    var util = ch.util,

        /**
         * Global instantiation widget id.
         * @private
         * @type {Number}
         */
        uid = 0;

    /**
     * Base class for all widgets.
     * @memberof ch
     * @constructor
     * @augments ch.EventEmitter
     * @param {Selector} $el Query Selector element.
     * @param {Object} [options] Configuration options.
     * @returns {Object}
     */
    function Widget($el, options) {
        this.init($el, options);

        return this;
    }

    ch.util.inherits(Widget, ch.EventEmitter);

    /**
     * The name of the widget. All instances are saved into a 'map', grouped by its name. You can reach for any or all of the components from a specific name with 'ch.instances'.
     * @memberof! ch.Widget.prototype
     * @type {String}
     */
    Widget.prototype.name = 'widget';

    /**
     * Returns a reference to the Constructor function that created the instance's prototype.
     * @memberof! ch.Widget.prototype
     * @function
     */
    Widget.prototype.constructor = Widget;

    /**
     * Initialize the instance and merges the user options with defaults options.
     * @memberof! ch.Widget.prototype
     * @function
     * @returns {instance}
     */
    Widget.prototype.init = function ($el, options) {

        // Clones defaults or creates a defaults object
        var defaults = (this._defaults) ? util.clone(this._defaults) : {};

        // Clones the defaults options or creates a new object
        if (options === undefined) {
            if ($el === undefined) {
                this._options = defaults;

            } else if (util.is$($el)) {
                this._$el = $el;
                this._el = $el[0];
                this._options = defaults;

            } else if (typeof $el === 'object') {
                options = $el;
                $el = undefined;
                this._options = $.extend(defaults, options);
            }

        } else if (typeof options === 'object') {
            if ($el === undefined) {
                this._options = $.extend(defaults, options);

            } else if (util.is$($el)) {
                this._$el = $el;
                this._el = $el[0];
                this._options = $.extend(defaults, options);
            }

        } else {
            throw new window.Error('Expected 2 parameters or less');
        }

        /**
         * Global instantiation Widget id.
         * @type {Number}
         */
        this.uid = (uid += 1);

        /**
         * Indicates if the widget is enabled.
         * @type {Boolean}
         * @private
         */
        this._enabled = true;
    };

    /**
     * Adds functionality or abilities from other classes.
     * @memberof! ch.Widget.prototype
     * @function
     * @returns {instance}
     */
    Widget.prototype.require = function () {

        var that = this;

        $.each(arguments, function (i, arg) {
            if (that[arg.toLowerCase()] === undefined) {
                ch[arg].call(that);
            }
        });
    };

    /**
     * Turns on the Widget.
     * @memberof! ch.Widget.prototype
     * @function
     * @returns {instance}
     */
    Widget.prototype.enable = function () {
        this._enabled = true;

        /**
         * Emits when the widget is enable.
         * @event ch.Widget#enable
         * @exampleDescription
         * @example
         * widget.on('enable', function(){
         *  // Some code here!
         * });
         */
        this.emit('enable');

        return this;
    };

    /**
     * Turns off the Widget.
     * @memberof! ch.Widget.prototype
     * @function
     * @returns {instance}
     */
    Widget.prototype.disable = function () {
        this._enabled = false;

        /**
         * Emits when the widget is disable.
         * @event ch.Widget#disable
         * @exampleDescription
         * @example
         * widget.on('disable', function(){
         *  // Some code here!
         * });
         */
        this.emit('disable');

        return this;
    };

    /**
     * Destroys a widget instance and remove data from its element.
     * @memberof! ch.Widget.prototype
     * @function
     */
    Widget.prototype.destroy = function () {

        this.disable();

        if (this._el !== undefined) {
            this._$el.removeData(this.name);
        }

        /**
         * Emits when the widget is destroyed.
         * @event ch.Widget#destroy
         * @exampleDescription
         * @example
         * widget.on('destroy', function(){
         *  // Some code here!
         * });
         */
        this.emit('destroy');
    };

    ch.Widget = Widget;

}(this, this.ch.$, this.ch));<commit_msg>Update Widget documentation.<commit_after>(function (window, $, ch) {
    'use strict';

    if (ch === undefined) {
        throw new window.Error('Expected ch namespace defined.');
    }

    var util = ch.util,

        /**
         * Global instantiation widget id.
         * @private
         * @type {Number}
         */
        uid = 0;

    /**
     * Base class for all widgets.
     * @memberof ch
     * @constructor
     * @augments ch.EventEmitter
     * @param {Selector} $el Query Selector element.
     * @param {Object} [options] Configuration options.
     * @returns {Object}
     */
    function Widget($el, options) {
        this.init($el, options);

        return this;
    }

    ch.util.inherits(Widget, ch.EventEmitter);

    /**
     * The name of the widget. A new instance is saved into the $el parameter.
     * @memberof! ch.Widget.prototype
     * @type {String}
     * @expample
     * // You can reach the instance associated.
     * var widget = $(selector).data('widget');
     */
    Widget.prototype.name = 'widget';

    /**
     * Returns a reference to the Constructor function that created the instance's prototype.
     * @memberof! ch.Widget.prototype
     * @constructor
     * @private
     */
    Widget.prototype._constructor = Widget;

    /**
     * Initialize the instance and merges the user options with defaults options.
     * @memberof! ch.Widget.prototype
     * @function
     * @returns {instance} Returns an instance of Widget.
     */
    Widget.prototype.init = function ($el, options) {

        // Clones defaults or creates a defaults object
        var defaults = (this._defaults) ? util.clone(this._defaults) : {};

        // Clones the defaults options or creates a new object
        if (options === undefined) {
            if ($el === undefined) {
                this._options = defaults;

            } else if (util.is$($el)) {
                this._$el = $el;
                this._el = $el[0];
                this._options = defaults;

            } else if (typeof $el === 'object') {
                options = $el;
                $el = undefined;
                this._options = $.extend(defaults, options);
            }

        } else if (typeof options === 'object') {
            if ($el === undefined) {
                this._options = $.extend(defaults, options);

            } else if (util.is$($el)) {
                this._$el = $el;
                this._el = $el[0];
                this._options = $.extend(defaults, options);
            }

        } else {
            throw new window.Error('Expected 2 parameters or less');
        }

        /**
         * Global instantiation Widget id.
         * @type {Number}
         */
        this.uid = (uid += 1);

        /**
         * Indicates if the widget is enabled.
         * @type {Boolean}
         * @private
         */
        this._enabled = true;
    };

    /**
     * Adds functionality or abilities from other classes.
     * @memberof! ch.Widget.prototype
     * @function
     * @returns {instance} Returns an instance of Widget.
     * @params {...String} var_args The name of the abilities to will be used.
     * @expample
     * // You can require some abilitiest to use in your widget.
     * // For example you should require the collpasible and closable abitliy.
     * var widget = new Widget(element, options);
     * widget.require('Collapsible', 'Closable');
     */
    Widget.prototype.require = function () {

        var that = this;

        $.each(arguments, function (i, arg) {
            if (that[arg.toLowerCase()] === undefined) {
                ch[arg].call(that);
            }
        });

        return this;
    };

    /**
     * Enables an instance of Widget.
     * @memberof! ch.Widget.prototype
     * @function
     * @returns {instance} Returns an instance of Widget.
     * @expample
     * // Enabling an instance of Widget.
     * widget.enable();
     */
    Widget.prototype.enable = function () {
        this._enabled = true;

        /**
         * Emits when the widget is enable.
         * @event ch.Widget#enable
         * @example
         * widget.on('enable', function () {
         *  // Some code here!
         * });
         */
        this.emit('enable');

        return this;
    };

    /**
     * Disables an instance of Widget.
     * @memberof! ch.Widget.prototype
     * @function
     * @returns {instance} Returns an instance of Widget.
     * @expample
     * // Disabling an instance of Widget.
     * widget.disable();
     */
    Widget.prototype.disable = function () {
        this._enabled = false;

        /**
         * Emits when the widget is disable.
         * @event ch.Widget#disable
         * @example
         * widget.on('disable', function () {
         *  // Some code here!
         * });
         */
        this.emit('disable');

        return this;
    };

    /**
     * Destroys an instance of Widget and remove its data from asociated element.
     * @memberof! ch.Widget.prototype
     * @function
     * @expample
     * // Destroying an instance of Widget.
     * widget.destroy();
     */
    Widget.prototype.destroy = function () {

        this.disable();

        if (this._el !== undefined) {
            this._$el.removeData(this.name);
        }

        /**
         * Emits when the widget is destroyed.
         * @event ch.Widget#destroy
         * @exampleDescription
         * @example
         * widget.on('destroy', function () {
         *  // Some code here!
         * });
         */
        this.emit('destroy');
    };

    ch.Widget = Widget;

}(this, this.ch.$, this.ch));<|endoftext|>


<commit_before>import { module, skip } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { click, render, settled, triggerEvent } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import {
  popoverArrowClass,
  popoverPositionClass,
  test,
  versionDependent,
  visibilityClass,
} from '../../helpers/bootstrap';
import { assertPositioning, setupForPositioning } from '../../helpers/contextual-help';
import setupStylesheetSupport from '../../helpers/setup-stylesheet-support';
import setupNoDeprecations from '../../helpers/setup-no-deprecations';
import { gte } from 'ember-compatibility-helpers';
import a11yAudit from 'ember-a11y-testing/test-support/audit';
import sinon from 'sinon';

module('Integration | Component | bs-popover', function (hooks) {
  setupRenderingTest(hooks);
  setupStylesheetSupport(hooks);
  setupNoDeprecations(hooks);

  test('it has correct markup', async function (assert) {
    // Template block usage:
    await render(hbs`
      <BsPopover @id="popover-element" @fade={{true}} @title="dummy title" @class="wide" @visible={{true}}>
        template block text
      </BsPopover>
    `);

    assert.dom('.popover').exists({ count: 1 }, 'has popover class');
    assert.dom('.popover.wide').exists({ count: 1 }, 'it passes along class attribute');
    assert.dom('.popover').hasClass('fade', 'has fade class');
    assert.dom('.popover').hasClass(visibilityClass(), 'has visibility class');
    assert.equal(this.element.querySelector('.popover').getAttribute('role'), 'tooltip', 'has ARIA role');
    assert.dom(`.${popoverArrowClass()}`).exists({ count: 1 }, 'has arrow');
    assert.dom(versionDependent('.popover-header')).hasText('dummy title', 'shows title');
    assert.dom(versionDependent('.popover-body')).hasText('template block text', 'shows content');
  });

  skip('it supports different placements', async function (assert) {
    this.insertCSSRule('#wrapper { margin: 200px }');

    let placements = ['top', 'left', 'bottom', 'right'];
    this.set('placement', placements[0]);
    await render(hbs`

        <BsPopover::Element @id="popover-element" @placement={{this.placement}} @title="dummy title">
          template block text
        </BsPopover::Element>

    `);

    for (let placement of placements) {
      this.set('placement', placement);
      let placementClass = popoverPositionClass(placement);
      assert.dom('.popover').hasClass(placementClass, `has ${placementClass} class`);
    }
  });

  test('should place popover on top of element', async function (assert) {
    this.insertCSSRule('#wrapper p { margin-top: 200px }');

    await render(hbs`

          <button type="button" class="btn" id="target">
            Click me<BsPopover @placement="top" @title="very very very very very very very long popover" @fade={{false}}>very very very very very very very long popover</BsPopover>
          </button>

`);

    setupForPositioning();

    await click('#target');

    assertPositioning(assert, '.popover', 8);
  });

  test('should adjust popover arrow', async function (assert) {
    this.insertCSSRule('#wrapper p { margin-top: 200px }');
    this.insertCSSRule('#target { width: 100px; padding: 0; border: none; }');

    let expectedArrowPosition = 217;

    await render(hbs`

          <button type="button" id="target">
            Click me<BsPopover @placement="top" @autoPlacement={{true}} @viewportSelector="#wrapper" @title="very very very very very very very long popover" @fade={{false}}>very very very very very very very long popover</BsPopover>
          </button>

`);

    setupForPositioning('right');

    await click('#target');
    let arrowPosition = parseInt(
      this.element
        .querySelector(`.${popoverArrowClass()}`)
        .style.transform.match(/translate(?:3d)?\(([0-9]+)px.*\)/)[1],
      10
    );
    assert.ok(
      Math.abs(arrowPosition - expectedArrowPosition) <= 2,
      `Expected position: ${expectedArrowPosition}, actual: ${arrowPosition}`
    );

    // check again to prevent regression of https://github.com/kaliber5/ember-bootstrap/issues/361
    await click('#target');
    await click('#target');
    arrowPosition = parseInt(
      this.element
        .querySelector(`.${popoverArrowClass()}`)
        .style.transform.match(/translate(?:3d)?\(([0-9]+)px.*\)/)[1],
      10
    );
    assert.ok(
      Math.abs(arrowPosition - expectedArrowPosition) <= 2,
      `Expected position: ${expectedArrowPosition}, actual: ${arrowPosition}`
    );
  });

  test('it stays open when clicked when rendered in place', async function (assert) {
    await render(hbs`

        <BsPopover>

Content

        </BsPopover>

    `);

    await click('#target');
    assert.dom('.popover').exists('popover is visible');
    await click('#content');
    assert.dom('.popover').exists('popover is still visible');
  });

  test('it yields close action', async function (assert) {
    let hideAction = sinon.spy();
    this.set('hide', hideAction);
    let hiddenAction = sinon.spy();
    this.set('hidden', hiddenAction);
    await render(hbs`

        <BsPopover @visible={{true}} @onHide={{action this.hide}} @onHidden={{action this.hidden}} as |po|>

Hide

        </BsPopover>

    `);
    await click('#hide');
    assert.ok(hideAction.calledOnce, 'hide action has been called');
    assert.ok(hiddenAction.calledOnce, 'hidden action was called');
    assert.dom('.popover').doesNotExist('popover is not visible');
  });

  test('click-initiated close action does not interfere with click-to-open', async function (assert) {
    await render(hbs`

        <BsPopover as |po|>

Hide

        </BsPopover>

    `);
    await click('#target');
    assert.dom('.popover').exists('popover is visible');
    await click('#hide');
    assert.dom('.popover').doesNotExist('popover is not visible');
    await click('#target');
    assert.dom('.popover').exists('popover visible again');
  });

  test('click-initiated close action does not interfere with click-to-open when wormholed', async function (assert) {
    await render(hbs`

        <BsPopover as |po|>
Hide
</BsPopover>

    `);
    await click('#target');
    assert.dom('.popover').exists('popover is visible');
    await click('#hide');
    assert.dom('.popover').doesNotExist('popover is not visible');
    await click('#target');
    assert.dom('.popover').exists('popover visible again');
  });

  test('non-click-initiated close action does not interfere with click-to-open', async function (assert) {
    await render(
      hbs`<div id="target"><BsPopover as |po|><input id="hide" onblur={{action po.close}}></BsPopover></div>`
    );
    await click('#target');
    assert.dom('.popover').exists('popover is visible');
    await triggerEvent('#hide', 'blur');
    assert.dom('.popover').doesNotExist('popover is not visible');
    await click('#target');
    assert.dom('.popover').exists('popover visible again');
  });

  test('non-click-initiated close action does not interfere with click-to-open when wormholed', async function (assert) {
    await render(
      hbs`<div id="ember-bootstrap-wormhole"></div><div id="target"><BsPopover as |po|><input id="hide" onblur={{action po.close}}></BsPopover></div>`
    );
    await click('#target');
    assert.dom('.popover').exists('popover is visible');
    await triggerEvent('#hide', 'blur');
    assert.dom('.popover').doesNotExist('popover is not visible');
    await click('#target');
    assert.dom('.popover').exists('popover visible again');
  });

  test('it passes along class attribute', async function (assert) {
    await render(hbs`<div id="target"><BsPopover @title="Dummy" @class="wide">test</BsPopover></div>`);
    await click('#target');
    assert.dom('.popover').hasClass('wide');
  });

  (gte('3.4.0') ? test : skip)('it passes all HTML attribute', async function (assert) {
    await render(
      hbs`<div id="target"><BsPopover @title="Dummy" class="wide" data-test role="foo">test</BsPopover></div>`
    );
    await click('#target');
    assert.dom('.popover').hasClass('wide');
    assert.dom('.popover').hasAttribute('role', 'foo');
    assert.dom('.popover').hasAttribute('data-test');
  });

  test("Renders in wormhole's default destination if renderInPlace is not set", async function (assert) {
    this.set('show', false);
    await render(
      hbs`<div id="ember-bootstrap-wormhole"></div>{{#if this.show}}<BsPopover @title="Simple popover" @visible={{true}} @fade={{false}} />{{/if}}`
    );
    this.set('show', true);
    await settled();

    assert.dom('#ember-bootstrap-wormhole .popover').exists({ count: 1 }, 'Popover exists in wormhole');
  });

  test('Renders in test container if renderInPlace is not set', async function (assert) {
    this.set('show', false);
    await render(hbs`{{#if this.show}}<BsPopover @title="Simple popover" @visible={{true}} @fade={{false}} />{{/if}}`);
    this.set('show', true);
    await settled();

    assert.dom('.popover').exists({ count: 1 }, 'Popover exists.');
    assert.dom('#wrapper .popover').doesNotExist();
  });

  test('Renders in place (no wormhole) if renderInPlace is set', async function (assert) {
    this.set('show', false);
    await render(
      hbs`<div id="ember-bootstrap-wormhole"></div><div id="wrapper">{{#if this.show}}<BsPopover @title="Simple popover" @visible={{true}} @fade={{false}} @renderInPlace={{true}} />{{/if}}</div>`
    );
    this.set('show', true);
    await settled();

    assert.dom('#wrapper .popover').exists({ count: 1 }, 'Popover exists in place.');
  });

  test('it calls onShow/onShown actions when showing popover by event [fade=false]', async function (assert) {
    let showAction = sinon.spy();
    this.set('show', showAction);
    let shownAction = sinon.spy();
    this.set('shown', shownAction);
    await render(
      hbs`<div id="target"><BsPopover @title="Dummy" @fade={{false}} @onShow={{this.show}} @onShown={{this.shown}} /></div>`
    );
    await click('#target');
    assert.ok(showAction.calledOnce, 'show action has been called');
    assert.ok(shownAction.calledOnce, 'shown action has been called');
  });

  test('it calls onShow/onShown actions when showing popover programmatically [fade=false]', async function (assert) {
    let showAction = sinon.spy();
    this.set('show', showAction);
    let shownAction = sinon.spy();
    this.set('shown', shownAction);
    this.set('visible', false);
    await render(
      hbs`<div id="target"><BsPopover @title="Dummy" @visible={{this.visible}} @fade={{false}} @onShow={{this.show}} @onShown={{this.shown}} /></div>`
    );
    this.set('visible', true);
    await settled();
    assert.ok(showAction.calledOnce, 'show action has been called');
    assert.ok(shownAction.calledOnce, 'shown action has been called');
  });

  test('it aborts showing if onShow action returns false', async function (assert) {
    let showAction = sinon.stub();
    showAction.returns(false);
    this.set('show', showAction);
    let shownAction = sinon.spy();
    this.set('shown', shownAction);
    await render(
      hbs`<div id="target"><BsPopover @title="Dummy" @fade={{false}} @onShow={{this.show}} @onShown={{this.shown}} /></div>`
    );
    await click('#target');
    assert.ok(showAction.calledOnce, 'show action has been called');
    assert.notOk(shownAction.calledOnce, 'show action has not been called');
    assert.dom('.popover').doesNotExist('popover is not visible');
  });

  test('it calls onHide/onHidden actions when hiding popover by event [fade=false]', async function (assert) {
    let hideAction = sinon.spy();
    this.set('hide', hideAction);
    let hiddenAction = sinon.spy();
    this.set('hidden', hiddenAction);
    await render(
      hbs`<div id="target"><BsPopover @title="Dummy" @fade={{false}} @onHide={{this.hide}} @onHidden={{this.hidden}} /></div>`
    );
    await click('#target');
    await click('#target');
    assert.ok(hideAction.calledOnce, 'hide action has been called');
    assert.ok(hiddenAction.calledOnce, 'hidden action was called');
  });

  test('it calls onHide/onHidden actions when hiding popover programmatically [fade=false]', async function (assert) {
    let hideAction = sinon.spy();
    this.set('hide', hideAction);
    let hiddenAction = sinon.spy();
    this.set('hidden', hiddenAction);
    this.set('visible', true);
    await render(
      hbs`<div id="target"><BsPopover @visible={{this.visible}} @title="Dummy" @fade={{false}} @onHide={{this.hide}} @onHidden={{this.hidden}} /></div>`
    );
    this.set('visible', false);
    await settled();
    assert.ok(hideAction.calledOnce, 'hide action has been called');
    assert.ok(hiddenAction.calledOnce, 'hidden action was called');
  });

  test('it aborts hiding if onHide action returns false', async function (assert) {
    let hideAction = sinon.stub();
    hideAction.returns(false);
    this.set('hide', hideAction);
    let hiddenAction = sinon.spy();
    this.set('hidden', hiddenAction);
    await render(
      hbs`<div id="target"><BsPopover @title="Dummy" @fade={{false}} @onHide={{this.hide}} @onHidden={{this.hidden}} /></div>`
    );
    await click('#target');
    await click('#target');
    assert.ok(hideAction.calledOnce, 'hide action has been called');
    assert.notOk(hiddenAction.calledOnce, 'hidden action has not been called');
    assert.dom('.popover').exists({ count: 1 }, 'popover is visible');
  });

  test('it passes accessibility checks', async function (assert) {
    await render(hbs`
      <button type="button">
        Test
        <BsPopover @title="dummy title" @visible={{true}}>
          template block text
        </BsPopover>
      </button>
    `);

    await a11yAudit();
    assert.ok(true, 'A11y audit passed');
  });
});
<commit_msg>Apply suggestions from code review<commit_after>import { module, skip } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { click, render, settled, triggerEvent } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import {
  popoverArrowClass,
  popoverPositionClass,
  test,
  versionDependent,
  visibilityClass,
} from '../../helpers/bootstrap';
import { assertPositioning, setupForPositioning } from '../../helpers/contextual-help';
import setupStylesheetSupport from '../../helpers/setup-stylesheet-support';
import setupNoDeprecations from '../../helpers/setup-no-deprecations';
import { gte } from 'ember-compatibility-helpers';
import a11yAudit from 'ember-a11y-testing/test-support/audit';
import sinon from 'sinon';

module('Integration | Component | bs-popover', function (hooks) {
  setupRenderingTest(hooks);
  setupStylesheetSupport(hooks);
  setupNoDeprecations(hooks);

  test('it has correct markup', async function (assert) {
    // Template block usage:
    await render(hbs`
      <BsPopover @id="popover-element" @fade={{true}} @title="dummy title" @class="wide" @visible={{true}}>
        template block text
      </BsPopover>
    `);

    assert.dom('.popover').exists({ count: 1 }, 'has popover class');
    assert.dom('.popover.wide').exists({ count: 1 }, 'it passes along class attribute');
    assert.dom('.popover').hasClass('fade', 'has fade class');
    assert.dom('.popover').hasClass(visibilityClass(), 'has visibility class');
    assert.equal(this.element.querySelector('.popover').getAttribute('role'), 'tooltip', 'has ARIA role');
    assert.dom(`.${popoverArrowClass()}`).exists({ count: 1 }, 'has arrow');
    assert.dom(versionDependent('.popover-header')).hasText('dummy title', 'shows title');
    assert.dom(versionDependent('.popover-body')).hasText('template block text', 'shows content');
  });

  skip('it supports different placements', async function (assert) {
    this.insertCSSRule('#wrapper { margin: 200px }');

    let placements = ['top', 'left', 'bottom', 'right'];
    this.set('placement', placements[0]);
    await render(hbs`

        <BsPopover::Element @id="popover-element" @placement={{this.placement}} @title="dummy title">
          template block text
        </BsPopover::Element>

    `);

    for (let placement of placements) {
      this.set('placement', placement);
      let placementClass = popoverPositionClass(placement);
      assert.dom('.popover').hasClass(placementClass, `has ${placementClass} class`);
    }
  });

  test('should place popover on top of element', async function (assert) {
    this.insertCSSRule('#wrapper p { margin-top: 200px }');

    await render(hbs`

          <button type="button" class="btn" id="target">
            Click me<BsPopover @placement="top" @title="very very very very very very very long popover" @fade={{false}}>very very very very very very very long popover</BsPopover>
          </button>

`);

    setupForPositioning();

    await click('#target');

    assertPositioning(assert, '.popover', 8);
  });

  test('should adjust popover arrow', async function (assert) {
    this.insertCSSRule('#wrapper p { margin-top: 200px }');
    this.insertCSSRule('#target { width: 100px; padding: 0; border: none; }');

    let expectedArrowPosition = 217;

    await render(hbs`

          <button type="button" id="target">
            Click me<BsPopover @placement="top" @autoPlacement={{true}} @viewportSelector="#wrapper" @title="very very very very very very very long popover" @fade={{false}}>very very very very very very very long popover</BsPopover>
          </button>

`);

    setupForPositioning('right');

    await click('#target');
    let arrowPosition = parseInt(
      this.element
        .querySelector(`.${popoverArrowClass()}`)
        .style.transform.match(/translate(?:3d)?\(([0-9]+)px.*\)/)[1],
      10
    );
    assert.ok(
      Math.abs(arrowPosition - expectedArrowPosition) <= 2,
      `Expected position: ${expectedArrowPosition}, actual: ${arrowPosition}`
    );

    // check again to prevent regression of https://github.com/kaliber5/ember-bootstrap/issues/361
    await click('#target');
    await click('#target');
    arrowPosition = parseInt(
      this.element
        .querySelector(`.${popoverArrowClass()}`)
        .style.transform.match(/translate(?:3d)?\(([0-9]+)px.*\)/)[1],
      10
    );
    assert.ok(
      Math.abs(arrowPosition - expectedArrowPosition) <= 2,
      `Expected position: ${expectedArrowPosition}, actual: ${arrowPosition}`
    );
  });

  test('it stays open when clicked when rendered in place', async function (assert) {
    await render(hbs`

        <BsPopover>

Content

        </BsPopover>

    `);

    await click('#target');
    assert.dom('.popover').exists('popover is visible');
    await click('#content');
    assert.dom('.popover').exists('popover is still visible');
  });

  test('it yields close action', async function (assert) {
    let hideAction = sinon.spy();
    this.set('hide', hideAction);
    let hiddenAction = sinon.spy();
    this.set('hidden', hiddenAction);
    await render(hbs`

        <BsPopover @visible={{true}} @onHide={{action this.hide}} @onHidden={{action this.hidden}} as |po|>

Hide

        </BsPopover>

    `);
    await click('#hide');
    assert.ok(hideAction.calledOnce, 'hide action has been called');
    assert.ok(hiddenAction.calledOnce, 'hidden action was called');
    assert.dom('.popover').doesNotExist('popover is not visible');
  });

  test('click-initiated close action does not interfere with click-to-open', async function (assert) {
    await render(hbs`

        <BsPopover as |po|>

Hide

        </BsPopover>

    `);
    await click('#target');
    assert.dom('.popover').exists('popover is visible');
    await click('#hide');
    assert.dom('.popover').doesNotExist('popover is not visible');
    await click('#target');
    assert.dom('.popover').exists('popover visible again');
  });

  test('click-initiated close action does not interfere with click-to-open when wormholed', async function (assert) {
    await render(hbs`

        <BsPopover as |po|>
Hide
</BsPopover>

    `);
    await click('#target');
    assert.dom('.popover').exists('popover is visible');
    await click('#hide');
    assert.dom('.popover').doesNotExist('popover is not visible');
    await click('#target');
    assert.dom('.popover').exists('popover visible again');
  });

  test('non-click-initiated close action does not interfere with click-to-open', async function (assert) {
    await render(
      hbs`<div id="target"><BsPopover as |po|><input id="hide" onblur={{action po.close}}></BsPopover></div>`
    );
    await click('#target');
    assert.dom('.popover').exists('popover is visible');
    await triggerEvent('#hide', 'blur');
    assert.dom('.popover').doesNotExist('popover is not visible');
    await click('#target');
    assert.dom('.popover').exists('popover visible again');
  });

  test('non-click-initiated close action does not interfere with click-to-open when wormholed', async function (assert) {
    await render(
      hbs`<div id="ember-bootstrap-wormhole"></div><div id="target"><BsPopover as |po|><input id="hide" onblur={{action po.close}}></BsPopover></div>`
    );
    await click('#target');
    assert.dom('.popover').exists('popover is visible');
    await triggerEvent('#hide', 'blur');
    assert.dom('.popover').doesNotExist('popover is not visible');
    await click('#target');
    assert.dom('.popover').exists('popover visible again');
  });

  test('it passes along class attribute', async function (assert) {
    await render(hbs`<div id="target"><BsPopover @title="Dummy" @class="wide">test</BsPopover></div>`);
    await click('#target');
    assert.dom('.popover').hasClass('wide');
  });

  (gte('3.4.0') ? test : skip)('it passes all HTML attribute', async function (assert) {
    await render(
      hbs`<div id="target"><BsPopover @title="Dummy" class="wide" data-test role="foo">test</BsPopover></div>`
    );
    await click('#target');
    assert.dom('.popover').hasClass('wide');
    assert.dom('.popover').hasAttribute('role', 'foo');
    assert.dom('.popover').hasAttribute('data-test');
  });

  test("Renders in wormhole's default destination if renderInPlace is not set", async function (assert) {
    this.set('show', false);
    await render(
      hbs`<div id="ember-bootstrap-wormhole"></div>{{#if this.show}}<BsPopover @title="Simple popover" @visible={{true}} @fade={{false}} />{{/if}}`
    );
    this.set('show', true);
    await settled();

    assert.dom('#ember-bootstrap-wormhole .popover').exists({ count: 1 }, 'Popover exists in wormhole');
  });

  test('Renders in test container if renderInPlace is not set', async function (assert) {
    this.set('show', false);
    await render(hbs`{{#if this.show}}<BsPopover @title="Simple popover" @visible={{true}} @fade={{false}} />{{/if}}`);
    this.set('show', true);
    await settled();

    assert.dom('.popover').exists({ count: 1 }, 'Popover exists.');
    assert.dom('#wrapper .popover').doesNotExist();
  });

  test('Renders in place (no wormhole) if renderInPlace is set', async function (assert) {
    this.set('show', false);
    await render(
      hbs`<div id="ember-bootstrap-wormhole"></div><div id="wrapper">{{#if this.show}}<BsPopover @title="Simple popover" @visible={{true}} @fade={{false}} @renderInPlace={{true}} />{{/if}}</div>`
    );
    this.set('show', true);
    await settled();

    assert.dom('#wrapper .popover').exists({ count: 1 }, 'Popover exists in place.');
  });

  test('it calls onShow/onShown actions when showing popover by event [fade=false]', async function (assert) {
    let showAction = sinon.spy();
    this.set('show', showAction);
    let shownAction = sinon.spy();
    this.set('shown', shownAction);
    await render(
      hbs`<div id="target"><BsPopover @title="Dummy" @fade={{false}} @onShow={{this.show}} @onShown={{this.shown}} /></div>`
    );
    await click('#target');
    assert.ok(showAction.calledOnce, 'show action has been called');
    assert.ok(shownAction.calledOnce, 'shown action has been called');
  });

  test('it calls onShow/onShown actions when showing popover programmatically [fade=false]', async function (assert) {
    let showAction = sinon.spy();
    this.set('show', showAction);
    let shownAction = sinon.spy();
    this.set('shown', shownAction);
    this.set('visible', false);
    await render(
      hbs`<div id="target"><BsPopover @title="Dummy" @visible={{this.visible}} @fade={{false}} @onShow={{this.show}} @onShown={{this.shown}} /></div>`
    );
    this.set('visible', true);
    await settled();
    assert.ok(showAction.calledOnce, 'show action has been called');
    assert.ok(shownAction.calledOnce, 'shown action has been called');
  });

  test('it aborts showing if onShow action returns false', async function (assert) {
    let showAction = sinon.stub();
    showAction.returns(false);
    this.set('show', showAction);
    let shownAction = sinon.spy();
    this.set('shown', shownAction);
    await render(
      hbs`<div id="target"><BsPopover @title="Dummy" @fade={{false}} @onShow={{this.show}} @onShown={{this.shown}} /></div>`
    );
    await click('#target');
    assert.ok(showAction.calledOnce, 'show action has been called');
    assert.ok(shownAction.notCalled, 'show action has not been called');
    assert.dom('.popover').doesNotExist('popover is not visible');
  });

  test('it calls onHide/onHidden actions when hiding popover by event [fade=false]', async function (assert) {
    let hideAction = sinon.spy();
    this.set('hide', hideAction);
    let hiddenAction = sinon.spy();
    this.set('hidden', hiddenAction);
    await render(
      hbs`<div id="target"><BsPopover @title="Dummy" @fade={{false}} @onHide={{this.hide}} @onHidden={{this.hidden}} /></div>`
    );
    await click('#target');
    await click('#target');
    assert.ok(hideAction.calledOnce, 'hide action has been called');
    assert.ok(hiddenAction.calledOnce, 'hidden action was called');
  });

  test('it calls onHide/onHidden actions when hiding popover programmatically [fade=false]', async function (assert) {
    let hideAction = sinon.spy();
    this.set('hide', hideAction);
    let hiddenAction = sinon.spy();
    this.set('hidden', hiddenAction);
    this.set('visible', true);
    await render(
      hbs`<div id="target"><BsPopover @visible={{this.visible}} @title="Dummy" @fade={{false}} @onHide={{this.hide}} @onHidden={{this.hidden}} /></div>`
    );
    this.set('visible', false);
    await settled();
    assert.ok(hideAction.calledOnce, 'hide action has been called');
    assert.ok(hiddenAction.calledOnce, 'hidden action was called');
  });

  test('it aborts hiding if onHide action returns false', async function (assert) {
    let hideAction = sinon.stub();
    hideAction.returns(false);
    this.set('hide', hideAction);
    let hiddenAction = sinon.spy();
    this.set('hidden', hiddenAction);
    await render(
      hbs`<div id="target"><BsPopover @title="Dummy" @fade={{false}} @onHide={{this.hide}} @onHidden={{this.hidden}} /></div>`
    );
    await click('#target');
    await click('#target');
    assert.ok(hideAction.calledOnce, 'hide action has been called');
    assert.ok(hiddenAction.notCalled, 'hidden action has not been called');
    assert.dom('.popover').exists({ count: 1 }, 'popover is visible');
  });

  test('it passes accessibility checks', async function (assert) {
    await render(hbs`
      <button type="button">
        Test
        <BsPopover @title="dummy title" @visible={{true}}>
          template block text
        </BsPopover>
      </button>
    `);

    await a11yAudit();
    assert.ok(true, 'A11y audit passed');
  });
});
<|endoftext|>


<commit_before>// Create UML class display from globals:
//       ClassDiagramClasses
//       ClassDiagramObjectProperties

// Class diagram library

var classDiagram = (function(){
    // Sizing constants
    var WIDTH = 150;
    var HEIGHT = 60;
    var SPACING = 50;
    var LABEL_WIDTH = 120;
    var LABEL_HEIGHT = 40;
    var ANON_WIDTH = 80;
    var ANON_HEIGHT = 40;

    // New shape types

    // Variant on uml.Class which omits the method block
    OwlClass = joint.shapes.basic.Generic.extend({

        markup: [
        '<g class="rotatable">',
        '<g class="scalable">',
        '<rect class="owl-class-name-rect"/><rect class="owl-class-attrs-rect"/>',
        '</g>',
        '<text class="owl-class-name-text"/><text class="owl-class-attrs-text"/>',
        '</g>'
        ].join(''),

        defaults: joint.util.deepSupplement({

            type: 'owl.OwlClass',

            attrs: {
                rect: { 'width': 200 },

                '.owl-class-name-rect': { 'stroke': 'black', 'stroke-width': 2, 'fill': '#cfe2f3' },
                '.owl-class-attrs-rect': { 'stroke': 'black', 'stroke-width': 2, 'fill': '#cfe2f3' },

                '.owl-class-name-text': {
                    'ref': '.owl-class-name-rect', 'ref-y': .5, 'ref-x': .5, 'text-anchor': 'middle', 'y-alignment': 'middle', 'font-weight': 'bold',
                    'fill': 'black', 'font-size': 12, 'font-family': 'Arial, helvetica, sans-serif'
                },
                '.owl-class-attrs-text': {
                    'ref': '.owl-class-attrs-rect', 'ref-y': 5, 'ref-x': 5,
                    'fill': 'black', 'font-size': 12, 'font-family': 'Arial, helvetica, sans-serif'
                }
            },

            name: [],
            attributes: []

        }, joint.shapes.basic.Generic.prototype.defaults),

        initialize: function() {

            this.on('change:name change:attributes change:methods', function() {
                this.updateRectangles();
                this.trigger('owl-update');
            }, this);

            this.updateRectangles();

            joint.shapes.basic.Generic.prototype.initialize.apply(this, arguments);
        },

        getClassName: function() {
            return this.get('name');
        },

        updateRectangles: function() {

            var attrs = this.get('attrs');

            var rects = [
            { type: 'name', text: this.getClassName() },
            { type: 'attrs', text: this.get('attributes') }
            ];

            var offsetY = 0;

            _.each(rects, function(rect) {

                var lines = _.isArray(rect.text) ? rect.text : [rect.text];
                var rectHeight = lines.length * 20 + 20;

                attrs['.owl-class-' + rect.type + '-text'].text = lines.join('\n');
                attrs['.owl-class-' + rect.type + '-rect'].height = rectHeight;
                attrs['.owl-class-' + rect.type + '-rect'].transform = 'translate(0,' + offsetY + ')';

                offsetY += rectHeight;
            });
        }
    });

    // Shape used for anonymous intersection classes
    var IntersectionClass = joint.shapes.basic.Generic.extend({

        markup: '<g class="rotatable"><g class="scalable"><rect/></g><text/></g>',

        defaults: joint.util.deepSupplement({

            type: 'basic.Rect',
            attrs: {
                'rect': {
                    fill: '#ffffff',
                    stroke: '#000000', 'stroke-width': 1,
                    width: ANON_WIDTH,  height: ANON_HEIGHT,
                    rx: 5, ry: 5
                },
                'text': {
                    fill: '#000000',
                    text: 'intersection',
                    'font-size': 12,
                    'ref-x': .5,
                    'ref-y': .5,
                    'text-anchor': 'middle',
                    'y-alignment': 'middle',
                    'font-family': 'Arial, helvetica, sans-serif'
                }
            }

        }, joint.shapes.basic.Generic.prototype.defaults)
    });

    var IntersectionClassOLD = joint.shapes.basic.Rect.extend({

        defaults: joint.util.deepSupplement({

            // see joint.css for more element styles
            attrs: {
                rect: {
                    width: ANON_WIDTH,  height: ANON_HEIGHT, rx: 5, ry: 5, 'stroke-width': 2, stroke: 'black', fill: 'white'
                },
                text: {
                    text: 'intersection', fill: 'black', 'font-size': 12, 'font-family': 'Arial, helvetica, sans-serif'
                }
            },

        }, joint.shapes.basic.Generic.prototype.defaults)

    });

    // A block label that relies on routing to choose directions of link up
    var LabelBlock = joint.shapes.basic.Generic.extend({

        markup: ['<g class="rotatable"><g class="scalable"><rect/></g>',
             '<text class="content"/>',
             '</g>'].join(''),

        defaults: joint.util.deepSupplement({

            type: 'owl.LabelBlock',

            // see joint.css for more element styles
            attrs: {
                rect: {
                    fill: 'white', stroke: 'white', width: LABEL_WIDTH,  height: LABEL_HEIGHT
                },
                text: {
                    fill: 'black', 'font-size': 12, 'font-family': 'Arial, helvetica, sans-serif'
                },
                '.content': {
                    text: '',
                    ref: 'rect',
                    'ref-x': .5,
                    'ref-y': .5,
                    'y-alignment': 'middle',
                    'x-alignment': 'middle'
                }
            },

            content: ''

        }, joint.shapes.basic.Generic.prototype.defaults),

        initialize: function() {
            var attrs = this.get('attrs');
            attrs['.content'].text = this.get('content');
            joint.shapes.basic.Generic.prototype.initialize.apply(this, arguments);
        },

        setDivContent: function(cell, content) {
            // Append the content to div as html.
            cell.attr({ div : {
                html: content
            }});
        }

    });

    // A block label with invisible connetion ports at top and bottom
    var LabelPorts = joint.shapes.basic.Generic.extend(_.extend({}, joint.shapes.basic.PortsModelInterface, {

        markup: '<g class="rotatable"><g class="scalable"><rect class="body"/></g><text class="label"/><g class="inPorts"/><g class="outPorts"/></g>',
        portMarkup: '<g class="port port<%= id %>"><circle class="port-body"/><text class="port-label"/></g>',

        defaults: joint.util.deepSupplement({

//            type: 'devs.Model',   // To match up to devs.ModelView
            type: 'owl.LabelPorts',

            size: { width: 1, height: 1 },

            inPorts: ['in'],
            outPorts: ['out'],

            attrs: {
                '.': { magnet: false },
                '.body': {
                    width: 150, height: 250, stroke: 'none'
                },
                '.port-body': {
                    r: 3, magnet: 'passive', stroke: 'none'
                },
                text: {
                    'pointer-events': 'none'
                },
                '.label': { text: '', 'ref-x': .5, 'ref-y': 10, ref: '.body', 'text-anchor': 'middle',
                             fill: '#000000', 'font-size': 12, 'font-family': 'Arial, helvetica, sans-serif' },
            }

        }, joint.shapes.basic.Generic.prototype.defaults),

        getPortAttrs: function(portName, index, total, selector, type) {

            var attrs = {};

            var portClass = 'port' + index;
            var portSelector = selector + '>.' + portClass;
            var portBodySelector = portSelector + '>.port-body';

            attrs[portBodySelector] = { port: { id: portName || _.uniqueId(type) , type: type } };
            attrs[portSelector] = { ref: '.body', 'ref-x': 0.5};

            if (selector === '.outPorts') { attrs[portSelector]['ref-dy'] = 0; }

            return attrs;
        }
    }));
    var LabelPortsView = joint.dia.ElementView.extend(joint.shapes.basic.PortsViewInterface);

    // Simple routed link, no arrows, used to connect source to label
    var PropertyIn = joint.dia.Link.extend({
        defaults: {
            type: 'owl.PropertyIn',
            router: {
                name: 'manhattan',
                args: {
                    startDirections: ['bottom'],
                    endDirections: ['top']
                }
            },
            connector: { name: 'rounded' }
        }
    });

    // Simple routed link with a plain filled end arrow, used to connect label to target
    var PropertyOut = joint.dia.Link.extend({
        defaults: {
            type: 'owl.PropertyOut',
            attrs: { '.marker-target': { d: 'M 10 0 L 0 5 L 10 10 z', fill: 'black' }, '.font-size' : 10},
            router: {
                name: 'manhattan',
                args: {
                    startDirections: [ 'bottom'],
                    endDirections: ['top', 'left', 'right']
                }
            },
            connector: { name: 'rounded' }
        }
    });

    // Subclass links
    SubClassOf = joint.dia.Link.extend({
        defaults: {
            type: 'owl.SubClassOf',
            attrs: { '.marker-target': { d: 'M 20 0 L 0 10 L 20 20 L 20 0 z', fill: 'white' }},
            router: {
                name: 'manhattan',
                args: {
                    startDirections: [ 'top'],
                    endDirections: ['bottom', 'right']
                }
            },
            connector: { name: 'rounded' }
        }
    });

    // Process the classes and properties
    var indexClass = function(cells, classIndex, spec) {
        var wraptext = joint.util.breakText(spec.label, { width: WIDTH });
        var cls = new OwlClass({
            size: {width: WIDTH, height: HEIGHT},
            name: wraptext
        });
        classIndex[spec.uri] = cls;
        cells.push(cls);
    };

    var linkProperty = function(cells, classIndex, source, target, labelText) {
        _.each(source, function(s){
            _.each(target, function(t){
                var wraptext = joint.util.breakText(labelText, { width: LABEL_WIDTH });
                var label = new LabelBlock({
                    size: {width: LABEL_WIDTH,  height: LABEL_HEIGHT},
                    content : wraptext
                });
                var inLink = new PropertyIn({
                    source: { id: s.id },
                    target: { id: label.id },
                });
                var outLink = new PropertyOut({
                    source: { id: label.id },
                    target: { id: t.id },
                });

                cells.push(label);
                cells.push(inLink);
                cells.push(outLink);
            });
        });
    };

    var lookupClassCells = function(classIndex, classes) {
        var classCells = [];
        _.each(classes, function(cls){
            var cell = classIndex[cls];
            if ( ! _.isUndefined(cell) ) {
                classCells.push(cell);
            }
        });
        return classCells;
    };

    var expandClassSets = function(cells, classIndex, classSpec) {
        if (_.isObject(classSpec)) {
            if (classSpec.type === 'unionOf') {
                return lookupClassCells(classIndex, classSpec.members);
            } else if (classSpec.type === 'intersectionOf') {
                var intersectionCell = new IntersectionClass({size: {width: ANON_WIDTH, height : ANON_HEIGHT}});
                cells.push(intersectionCell);
                _.each(classSpec.members, function(member){
                    var memberCell = classIndex[member];
                    if (!_.isUndefined(memberCell)) {
                        var inLink = new PropertyIn({
                            source: { id: memberCell.id },
                            target: { id: intersectionCell.id },
                        });
                        cells.push(inLink);
                    }
                });
                return [ intersectionCell ];
            }
        } else if (_.isString(classSpec)) {
            return lookupClassCells(classIndex, [classSpec]);
        }
        // Internal error, should not happen
        console.log("Cannot parse class specification" + classSpec);
        return [];
    };

    var addProperty = function(cells, classIndex, domain, range, labelText) {
        linkProperty(cells, classIndex,
            expandClassSets(cells, classIndex, domain),
            expandClassSets(cells, classIndex, range), labelText);
    };

    var layout = function() {
        var cells = [];
        var classIndex = {};

        _.each(ClassDiagramClasses, _.partial(indexClass, cells, classIndex));
        _.each(ClassDiagramObjectProperties, function(spec){
            addProperty(cells, classIndex, spec.domain, spec.range, spec.label);
        });
        _.each(ClassDiagramClasses, function(spec){
            var sub = classIndex[spec.uri];
            _.each(spec.superClasses, function(supURI){
                var sup = classIndex[supURI];
                if ( ! _.isUndefined(sup) ) {
                    var scLink = new SubClassOf({
                        source: { id: sub.id },
                        target: { id: sup.id },
                    });
                    cells.push(scLink);
                };
            });
        });

        graph.resetCells( cells );
        joint.layout.DirectedGraph.layout(graph, { setLinkVertices: false });
    }

    // Inject custom views into the joints.shapes namespace
    joint.shapes.owl = {
        OwlClass       : OwlClass,
        LabelPorts     : LabelPorts,
        LabelPortsView : LabelPortsView,
        PropertyOut    : PropertyOut,
        PropertyIn     : PropertyIn
    };

    // Return module contents
    return {
        layout: layout
    };
})();

// Create the layout
var graph = new joint.dia.Graph;

var paper = new joint.dia.Paper({
    el: $('#paper'),
    width: 1200,
    height: 1000,
    gridSize: 1,
    model: graph
});

V(paper.viewport).translate(10, 25);     // Just give the viewport a little padding.
classDiagram.layout();
<commit_msg>Clean out unused code fragments<commit_after>// Create UML class display from globals:
//       ClassDiagramClasses
//       ClassDiagramObjectProperties

// Class diagram library

var classDiagram = (function(){
    // Sizing constants
    var WIDTH = 150;
    var HEIGHT = 60;
    var SPACING = 50;
    var LABEL_WIDTH = 120;
    var LABEL_HEIGHT = 40;
    var ANON_WIDTH = 80;
    var ANON_HEIGHT = 40;

    // New shape types

    // Variant on uml.Class which omits the method block
    OwlClass = joint.shapes.basic.Generic.extend({

        markup: [
        '<g class="rotatable">',
        '<g class="scalable">',
        '<rect class="owl-class-name-rect"/><rect class="owl-class-attrs-rect"/>',
        '</g>',
        '<text class="owl-class-name-text"/><text class="owl-class-attrs-text"/>',
        '</g>'
        ].join(''),

        defaults: joint.util.deepSupplement({

            type: 'owl.OwlClass',

            attrs: {
                rect: { 'width': 200 },

                '.owl-class-name-rect': { 'stroke': 'black', 'stroke-width': 2, 'fill': '#cfe2f3' },
                '.owl-class-attrs-rect': { 'stroke': 'black', 'stroke-width': 2, 'fill': '#cfe2f3' },

                '.owl-class-name-text': {
                    'ref': '.owl-class-name-rect', 'ref-y': .5, 'ref-x': .5, 'text-anchor': 'middle', 'y-alignment': 'middle', 'font-weight': 'bold',
                    'fill': 'black', 'font-size': 12, 'font-family': 'Arial, helvetica, sans-serif'
                },
                '.owl-class-attrs-text': {
                    'ref': '.owl-class-attrs-rect', 'ref-y': 5, 'ref-x': 5,
                    'fill': 'black', 'font-size': 12, 'font-family': 'Arial, helvetica, sans-serif'
                }
            },

            name: [],
            attributes: []

        }, joint.shapes.basic.Generic.prototype.defaults),

        initialize: function() {

            this.on('change:name change:attributes change:methods', function() {
                this.updateRectangles();
                this.trigger('owl-update');
            }, this);

            this.updateRectangles();

            joint.shapes.basic.Generic.prototype.initialize.apply(this, arguments);
        },

        getClassName: function() {
            return this.get('name');
        },

        updateRectangles: function() {

            var attrs = this.get('attrs');

            var rects = [
            { type: 'name', text: this.getClassName() },
            { type: 'attrs', text: this.get('attributes') }
            ];

            var offsetY = 0;

            _.each(rects, function(rect) {

                var lines = _.isArray(rect.text) ? rect.text : [rect.text];
                var rectHeight = lines.length * 20 + 20;

                attrs['.owl-class-' + rect.type + '-text'].text = lines.join('\n');
                attrs['.owl-class-' + rect.type + '-rect'].height = rectHeight;
                attrs['.owl-class-' + rect.type + '-rect'].transform = 'translate(0,' + offsetY + ')';

                offsetY += rectHeight;
            });
        }
    });

    // Shape used for anonymous intersection classes
    var IntersectionClass = joint.shapes.basic.Generic.extend({

        markup: '<g class="rotatable"><g class="scalable"><rect/></g><text/></g>',

        defaults: joint.util.deepSupplement({

            type: 'basic.Rect',
            attrs: {
                'rect': {
                    fill: '#ffffff',
                    stroke: '#000000', 'stroke-width': 1,
                    width: ANON_WIDTH,  height: ANON_HEIGHT,
                    rx: 5, ry: 5
                },
                'text': {
                    fill: '#000000',
                    text: 'intersection',
                    'font-size': 12,
                    'ref-x': .5,
                    'ref-y': .5,
                    'text-anchor': 'middle',
                    'y-alignment': 'middle',
                    'font-family': 'Arial, helvetica, sans-serif'
                }
            }

        }, joint.shapes.basic.Generic.prototype.defaults)
    });

    // A block label that relies on routing to choose directions of link up
    var LabelBlock = joint.shapes.basic.Generic.extend({

        markup: ['<g class="rotatable"><g class="scalable"><rect/></g>',
             '<text class="content"/>',
             '</g>'].join(''),

        defaults: joint.util.deepSupplement({

            type: 'owl.LabelBlock',

            // see joint.css for more element styles
            attrs: {
                rect: {
                    fill: 'white', stroke: 'white', width: LABEL_WIDTH,  height: LABEL_HEIGHT
                },
                text: {
                    fill: 'black', 'font-size': 12, 'font-family': 'Arial, helvetica, sans-serif'
                },
                '.content': {
                    text: '',
                    ref: 'rect',
                    'ref-x': .5,
                    'ref-y': .5,
                    'y-alignment': 'middle',
                    'x-alignment': 'middle'
                }
            },

            content: ''

        }, joint.shapes.basic.Generic.prototype.defaults),

        initialize: function() {
            var attrs = this.get('attrs');
            attrs['.content'].text = this.get('content');
            joint.shapes.basic.Generic.prototype.initialize.apply(this, arguments);
        },

        setDivContent: function(cell, content) {
            // Append the content to div as html.
            cell.attr({ div : {
                html: content
            }});
        }

    });

    // A block label with invisible connetion ports at top and bottom
    var LabelPorts = joint.shapes.basic.Generic.extend(_.extend({}, joint.shapes.basic.PortsModelInterface, {

        markup: '<g class="rotatable"><g class="scalable"><rect class="body"/></g><text class="label"/><g class="inPorts"/><g class="outPorts"/></g>',
        portMarkup: '<g class="port port<%= id %>"><circle class="port-body"/><text class="port-label"/></g>',

        defaults: joint.util.deepSupplement({

//            type: 'devs.Model',   // To match up to devs.ModelView
            type: 'owl.LabelPorts',

            size: { width: 1, height: 1 },

            inPorts: ['in'],
            outPorts: ['out'],

            attrs: {
                '.': { magnet: false },
                '.body': {
                    width: 150, height: 250, stroke: 'none'
                },
                '.port-body': {
                    r: 3, magnet: 'passive', stroke: 'none'
                },
                text: {
                    'pointer-events': 'none'
                },
                '.label': { text: '', 'ref-x': .5, 'ref-y': 10, ref: '.body', 'text-anchor': 'middle',
                             fill: '#000000', 'font-size': 12, 'font-family': 'Arial, helvetica, sans-serif' },
            }

        }, joint.shapes.basic.Generic.prototype.defaults),

        getPortAttrs: function(portName, index, total, selector, type) {

            var attrs = {};

            var portClass = 'port' + index;
            var portSelector = selector + '>.' + portClass;
            var portBodySelector = portSelector + '>.port-body';

            attrs[portBodySelector] = { port: { id: portName || _.uniqueId(type) , type: type } };
            attrs[portSelector] = { ref: '.body', 'ref-x': 0.5};

            if (selector === '.outPorts') { attrs[portSelector]['ref-dy'] = 0; }

            return attrs;
        }
    }));
    var LabelPortsView = joint.dia.ElementView.extend(joint.shapes.basic.PortsViewInterface);

    // Simple routed link, no arrows, used to connect source to label
    var PropertyIn = joint.dia.Link.extend({
        defaults: {
            type: 'owl.PropertyIn',
            router: {
                name: 'manhattan',
                args: {
                    startDirections: ['bottom'],
                    endDirections: ['top']
                }
            },
            connector: { name: 'rounded' }
        }
    });

    // Simple routed link with a plain filled end arrow, used to connect label to target
    var PropertyOut = joint.dia.Link.extend({
        defaults: {
            type: 'owl.PropertyOut',
            attrs: { '.marker-target': { d: 'M 10 0 L 0 5 L 10 10 z', fill: 'black' }, '.font-size' : 10},
            router: {
                name: 'manhattan',
                args: {
                    startDirections: [ 'bottom'],
                    endDirections: ['top', 'left', 'right']
                }
            },
            connector: { name: 'rounded' }
        }
    });

    // Subclass links
    SubClassOf = joint.dia.Link.extend({
        defaults: {
            type: 'owl.SubClassOf',
            attrs: { '.marker-target': { d: 'M 20 0 L 0 10 L 20 20 L 20 0 z', fill: 'white' }},
            router: {
                name: 'manhattan',
                args: {
                    startDirections: [ 'top'],
                    endDirections: ['bottom', 'right']
                }
            },
            connector: { name: 'rounded' }
        }
    });

    // Process the classes and properties
    var indexClass = function(cells, classIndex, spec) {
        var wraptext = joint.util.breakText(spec.label, { width: WIDTH });
        var cls = new OwlClass({
            size: {width: WIDTH, height: HEIGHT},
            name: wraptext
        });
        classIndex[spec.uri] = cls;
        cells.push(cls);
    };

    var linkProperty = function(cells, classIndex, source, target, labelText) {
        _.each(source, function(s){
            _.each(target, function(t){
                var wraptext = joint.util.breakText(labelText, { width: LABEL_WIDTH });
                var label = new LabelBlock({
                    size: {width: LABEL_WIDTH,  height: LABEL_HEIGHT},
                    content : wraptext
                });
                var inLink = new PropertyIn({
                    source: { id: s.id },
                    target: { id: label.id },
                });
                var outLink = new PropertyOut({
                    source: { id: label.id },
                    target: { id: t.id },
                });

                cells.push(label);
                cells.push(inLink);
                cells.push(outLink);
            });
        });
    };

    var lookupClassCells = function(classIndex, classes) {
        var classCells = [];
        _.each(classes, function(cls){
            var cell = classIndex[cls];
            if ( ! _.isUndefined(cell) ) {
                classCells.push(cell);
            }
        });
        return classCells;
    };

    var expandClassSets = function(cells, classIndex, classSpec) {
        if (_.isObject(classSpec)) {
            if (classSpec.type === 'unionOf') {
                return lookupClassCells(classIndex, classSpec.members);
            } else if (classSpec.type === 'intersectionOf') {
                var intersectionCell = new IntersectionClass({size: {width: ANON_WIDTH, height : ANON_HEIGHT}});
                cells.push(intersectionCell);
                _.each(classSpec.members, function(member){
                    var memberCell = classIndex[member];
                    if (!_.isUndefined(memberCell)) {
                        var inLink = new PropertyIn({
                            source: { id: memberCell.id },
                            target: { id: intersectionCell.id },
                        });
                        cells.push(inLink);
                    }
                });
                return [ intersectionCell ];
            }
        } else if (_.isString(classSpec)) {
            return lookupClassCells(classIndex, [classSpec]);
        }
        // Internal error, should not happen
        console.log("Cannot parse class specification" + classSpec);
        return [];
    };

    var addProperty = function(cells, classIndex, domain, range, labelText) {
        linkProperty(cells, classIndex,
            expandClassSets(cells, classIndex, domain),
            expandClassSets(cells, classIndex, range), labelText);
    };

    var layout = function() {
        var cells = [];
        var classIndex = {};

        _.each(ClassDiagramClasses, _.partial(indexClass, cells, classIndex));
        _.each(ClassDiagramObjectProperties, function(spec){
            addProperty(cells, classIndex, spec.domain, spec.range, spec.label);
        });
        _.each(ClassDiagramClasses, function(spec){
            var sub = classIndex[spec.uri];
            _.each(spec.superClasses, function(supURI){
                var sup = classIndex[supURI];
                if ( ! _.isUndefined(sup) ) {
                    var scLink = new SubClassOf({
                        source: { id: sub.id },
                        target: { id: sup.id },
                    });
                    cells.push(scLink);
                };
            });
        });

        graph.resetCells( cells );
        joint.layout.DirectedGraph.layout(graph, { setLinkVertices: false });
    }

    // Inject custom views into the joints.shapes namespace
    joint.shapes.owl = {
        OwlClass       : OwlClass,
        LabelPorts     : LabelPorts,
        LabelPortsView : LabelPortsView,
        PropertyOut    : PropertyOut,
        PropertyIn     : PropertyIn
    };

    // Return module contents
    return {
        layout: layout
    };
})();

// Create the layout
var graph = new joint.dia.Graph;

var paper = new joint.dia.Paper({
    el: $('#paper'),
    width: 1200,
    height: 1000,
    gridSize: 1,
    model: graph
});

V(paper.viewport).translate(10, 25);     // Just give the viewport a little padding.
classDiagram.layout();
<|endoftext|>


<commit_before>import { observable, computed, action, runInAction } from "mobx";

function roundToTens(percent) {
    return Math.round(percent / 10.0) * 10;
}

function toDrivingMode(disengageType) {
    switch (disengageType) {
        case "DISENGAGE_MANUAL":
            return "MANUAL";
        case "DISENGAGE_NONE":
            return "AUTO";
        case "DISENGAGE_EMERGENCY":
            return "DISENGAGED";
        case "DISENGAGE_AUTO_STEER_ONLY":
            return "AUTO STEER";
        case "DISENGAGE_AUTO_SPEED_ONLY":
            return "AUTO SPEED";
        case "DISENGAGE_CHASSIS_ERROR":
            return "CHASSIS ERROR";
        default:
            return "?";
    }
}

function isAutoMode(disengageType) {
    return disengageType === "DISENGAGE_NONE" ||
           disengageType === "DISENGAGE_AUTO_STEER_ONLY" ||
           disengageType === "DISENGAGE_AUTO_SPEED_ONLY";
}

function meterPerSecondToKmPerHour(speed) {
    return Math.round(speed * 3600 / 1000.0);
}

export default class Meters {
    @observable throttlePercent = 0;
    @observable brakePercent = 0;
    @observable speed = 0;
    @observable steeringAngle = 0;
    @observable steeringPercentage = 0;
    @observable drivingMode = "?";
    @observable isAutoMode = false;
    @observable turnSignal = "";

    @action update(world) {
        if (world.autoDrivingCar) {
            if (world.autoDrivingCar.throttlePercentage !== undefined) {
                this.throttlePercent = roundToTens(world.autoDrivingCar.throttlePercentage);
            }
            if (world.autoDrivingCar.brakePercentage !== undefined) {
                this.brakePercent = roundToTens(world.autoDrivingCar.brakePercentage);
            }

            if (world.autoDrivingCar.speed !== undefined) {
                // Convert the unit from m/s to mph.
                this.speed = world.autoDrivingCar.speed;
            }

            if (world.autoDrivingCar.steeringPercentage !== undefined) {
                this.steeringPercentage = Math.round(world.autoDrivingCar.steeringPercentage);
            }

            if (world.autoDrivingCar.steeringAngle !== undefined) {
                this.steeringAngle = -Math.round(
                    world.autoDrivingCar.steeringAngle * 180.0 / Math.PI);
            }

            if (world.autoDrivingCar.disengageType !== undefined) {
                this.drivingMode = toDrivingMode(world.autoDrivingCar.disengageType);
                this.isAutoMode = isAutoMode(world.autoDrivingCar.disengageType);
            }

            if (world.autoDrivingCar.currentSignal !== undefined) {
                this.turnSignal = world.autoDrivingCar.currentSignal;
            }
        }
    }
}
<commit_msg>Dreamview: updated steering angle and percentage validation<commit_after>import { observable, computed, action, runInAction } from "mobx";

function roundToTens(percent) {
    return Math.round(percent / 10.0) * 10;
}

function toDrivingMode(disengageType) {
    switch (disengageType) {
        case "DISENGAGE_MANUAL":
            return "MANUAL";
        case "DISENGAGE_NONE":
            return "AUTO";
        case "DISENGAGE_EMERGENCY":
            return "DISENGAGED";
        case "DISENGAGE_AUTO_STEER_ONLY":
            return "AUTO STEER";
        case "DISENGAGE_AUTO_SPEED_ONLY":
            return "AUTO SPEED";
        case "DISENGAGE_CHASSIS_ERROR":
            return "CHASSIS ERROR";
        default:
            return "?";
    }
}

function isAutoMode(disengageType) {
    return disengageType === "DISENGAGE_NONE" ||
           disengageType === "DISENGAGE_AUTO_STEER_ONLY" ||
           disengageType === "DISENGAGE_AUTO_SPEED_ONLY";
}

function meterPerSecondToKmPerHour(speed) {
    return Math.round(speed * 3600 / 1000.0);
}

export default class Meters {
    @observable throttlePercent = 0;
    @observable brakePercent = 0;
    @observable speed = 0;
    @observable steeringAngle = 0;
    @observable steeringPercentage = 0;
    @observable drivingMode = "?";
    @observable isAutoMode = false;
    @observable turnSignal = "";

    @action update(world) {
        if (world.autoDrivingCar) {
            if (world.autoDrivingCar.throttlePercentage !== undefined) {
                this.throttlePercent = roundToTens(world.autoDrivingCar.throttlePercentage);
            }
            if (world.autoDrivingCar.brakePercentage !== undefined) {
                this.brakePercent = roundToTens(world.autoDrivingCar.brakePercentage);
            }

            if (world.autoDrivingCar.speed !== undefined) {
                // Convert the unit from m/s to mph.
                this.speed = world.autoDrivingCar.speed;
            }

            if (world.autoDrivingCar.steeringPercentage !== undefined &&
                !isNaN(world.autoDrivingCar.steeringPercentage)) {
                this.steeringPercentage = Math.round(world.autoDrivingCar.steeringPercentage);
            }

            if (world.autoDrivingCar.steeringAngle !== undefined &&
                !isNaN(world.autoDrivingCar.steeringAngle)) {
                this.steeringAngle = -Math.round(
                    world.autoDrivingCar.steeringAngle * 180.0 / Math.PI);
            }

            if (world.autoDrivingCar.disengageType !== undefined) {
                this.drivingMode = toDrivingMode(world.autoDrivingCar.disengageType);
                this.isAutoMode = isAutoMode(world.autoDrivingCar.disengageType);
            }

            if (world.autoDrivingCar.currentSignal !== undefined) {
                this.turnSignal = world.autoDrivingCar.currentSignal;
            }
        }
    }
}
<|endoftext|>


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

test('compiles markdown', t => {
  const input = '# Hello world';
  const output = md.render(input);
  const expected = '
Hello world
\n';
  t.is(output, expected);
});

test('compiles code using Prism', t => {
  const input = `\`\`\`js
const do = input => {
  console.log(input)
}
\`\`\``;
  const output = md.render(input);
  const expected = '
const do = input => {\n  console.log(input)\n}\n
\n';
  t.is(output, expected);
});

test('transforms definition lists', t => {
  const input = `Term 1

: Definition 1

Term 2 with *inline markup*

: Definition 2

  Second paragraph of definition 2.`;
  const output = md.render(input);
  const expected = '
\n
Term 1
\n
\n
Definition 1
\n
\n
Term 2 with inline markup
\n
\n
Definition 2
\n
Second paragraph of definition 2.
\n
\n
\n';
  t.is(output, expected);
});

test('transforms footnotes', t => {
  const input = `Here is an inline note.^[Inlines notes are easier to write, since you don't have to pick an identifier and move down to type the note.]`;
  const output = md.render(input);
  const expected = '
Here is an inline note.[1]
\n
\n
\n
\n

Inlines notes are easier to write, since you don’t have to pick an identifier and move down to type the note. ↩
\n
\n
\n
\n';
  t.is(output, expected);
});

test('transforms superscript', t => {
  const input = '29^th^';
  const output = md.render(input);
  const expected = '
29th
\n';
  t.is(output, expected);
});

test('transforms emoji', t => {
  const input = ':tada:';
  const output = md.render(input);
  const expected = '
🎉
\n';
  t.is(output, expected);
});

test('transforms attributes', t => {
  const input = '# header {.style-me}\nparagraph {data-toggle=modal}';
  const output = md.render(input);
  const expected = '
header
\n
paragraph
\n';
  t.is(output, expected);
});

test('transforms emmet containers', t => {
  const input = `!!! aside.warning
*Here be dragons*
!!!`;
  const output = md.render(input);
  const expected = '
Here be dragons
\n';
  t.is(output, expected);
});
<commit_msg>💚 (static-site-scaffold) Fix broken markdown tests<commit_after>/**
 * Copyright 2019 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
const test = require('ava');
const md = require('../lib/11ty/markdown');

test('compiles markdown', t => {
  const input = '# Hello world';
  const output = md.render(input);
  const expected = '
\n
Hello world
\n
\n';
  t.is(output, expected);
});

test('compiles code using Prism', t => {
  const input = `\`\`\`js
const do = input => {
  console.log(input)
}
\`\`\``;
  const output = md.render(input);
  const expected = '
const do = input => {\n  console.log(input)\n}\n
\n';
  t.is(output, expected);
});

test('transforms definition lists', t => {
  const input = `Term 1

: Definition 1

Term 2 with *inline markup*

: Definition 2

  Second paragraph of definition 2.`;
  const output = md.render(input);
  const expected = '
\n
Term 1
\n
\n
Definition 1
\n
\n
Term 2 with inline markup
\n
\n
Definition 2
\n
Second paragraph of definition 2.
\n
\n
\n';
  t.is(output, expected);
});

test('transforms footnotes', t => {
  const input = `Here is an inline note.^[Inlines notes are easier to write, since you don't have to pick an identifier and move down to type the note.]`;
  const output = md.render(input);
  const expected = '
Here is an inline note.[1]
\n
\n
\n
\n

Inlines notes are easier to write, since you don’t have to pick an identifier and move down to type the note. ↩
\n
\n
\n
\n';
  t.is(output, expected);
});

test('transforms superscript', t => {
  const input = '29^th^';
  const output = md.render(input);
  const expected = '
29th
\n';
  t.is(output, expected);
});

test('transforms emoji', t => {
  const input = ':tada:';
  const output = md.render(input);
  const expected = '
🎉
\n';
  t.is(output, expected);
});

test('transforms attributes', t => {
  const input = '# header {.style-me}\nparagraph {data-toggle=modal}';
  const output = md.render(input);
  const expected = '
\n
header
\n
paragraph
\n
\n';
  t.is(output, expected);
});

test('transforms emmet containers', t => {
  const input = `!!! aside.warning
*Here be dragons*
!!!`;
  const output = md.render(input);
  const expected = '
Here be dragons
\n';
  t.is(output, expected);
});
<|endoftext|>


<commit_before>"use strict"

var fs = require("fs");
var path = require("path");

var aws = require("aws-sdk");
var glob = require("glob");
var jsonFile = require("jsonfile");
var emptyObject = require("empty-object");
var forEach = require("foreach");

function getEC2Client(regionName) {
	return new aws.EC2( { region: regionName } );
}

function getRegionNames(regionName, callback) {
	getEC2Client(regionName).describeRegions(function(err, result) {
		if (err) callback(err);
		else {
			var names = result.Regions.map(function(o) { return o.RegionName; });
			callback(null, names);
		}
	});
}

function getConfigPaths(pathPatterns, workingDirectory, callback) {
	if (typeof workingDirectory != "string") workingDirectory = process.cwd();
	if (!Array.isArray(pathPatterns) || pathPatterns.length == 0) {
		pathPatterns = [ "*.cfnpp" ];
	}
	pathPatterns.forEach(function(pattern) {
		glob(pattern, function (err, paths) {
			if (err) callback(err);
			else {
				paths = paths.map(function(o) { return path.resolve(workingDirectory, o); } );
				callback(null, paths);
			}
		});
	});
}

function getAmiMappings(config) {
	var mappings = [];
	forEach(config.amiMappings, function(mapping, key) {
		mappings.push({
			"mappingKey": key,
			"owner": mapping.owner || "amazon",
			"name": mapping.name
		});
	});
	return mappings;
}

function getFileIncludes(config, relativeDirectory) {
	var fileIncludes = [];
	forEach(config.fileIncludes, function(fileInclude, resourceKey) {
		forEach(fileInclude, function(fileIncludeConfig, configKey) {
			forEach(fileIncludeConfig, function(fileIncludeName, fileKey){
				fileIncludes.push({
					"resourceKey": resourceKey,
					"configKey": configKey,
					"fileKey": fileKey,
					"includePath": path.resolve(relativeDirectory, fileIncludeName)
				})
			})
		});
	});
	return fileIncludes;
}

function getAmiId(regionName, owner, name, callback) {
	getEC2Client(regionName).describeImages({
		Owners: [ owner ],
		Filters: [ { Name: "name", Values: [ name ] } ]
	}, function(err, data) {
		if (err) callback(err);
		else {
			var images = data.Images.sort(function(a,b) { f
				var a = a.Name;
				var b = b.Name;
				return (a > b) ? 1 : ((a < b) ? -1 : 0);
			});
			if (images.length == 0) callback(new Error("AMI owned by '" + owner + "' and named '" + name + "' not found"));
			else callback(null, images[0].ImageId);
		}
	});
}

function taskComplete(tasksCount, templatePath, processedTemplatePath, template, compact) {
	if (tasksCount == 0) {
		jsonFile.spaces = compact ? 0 : 2;
		jsonFile.writeFile(processedTemplatePath, template, function(err) {
			if (err) console.error("Error saving template '" + processedTemplatePath + "': " + err);
			else console.log("Template '" + templatePath + "' pre-processed to '" + processedTemplatePath + "'");
		});
	}
}

function processTemplate(config, regionNames, templatePath, compact, processedTemplatePath) {
	console.log("Template path is " + templatePath);
	jsonFile.readFile(templatePath, function (err, template) {
		//console.log("Tenplate is " + JSON.stringify(template));
		if (err) console.error("Error reading '" + templatePath + "': " + err);
		else {
			var amiMappings = getAmiMappings(config);
			var amiMappingsCount = amiMappings.length;

			var fileIncludes = getFileIncludes(config, path.dirname(templatePath));
			var fileIncludesCount = fileIncludes.length;

			console.log("Template '" + templatePath + "': " + amiMappingsCount + " AMI mappings and " + fileIncludesCount + " file-includes");

			var tasksCount = (amiMappingsCount * regionNames.length) + fileIncludesCount;

			var mappings = template.Mappings;
			amiMappings.forEach(function(amiMappingTask) {
				var mappingKey = amiMappingTask.mappingKey;
				var owner = amiMappingTask.owner;
				var name = amiMappingTask.name;
				console.log("Template '" + templatePath + "', mapping '" + mappingKey + "': searching for last AMI owned by '" + owner + "' and named '" + name + "'");
				var mapping = mappings[mappingKey];
				emptyObject(mapping);
				regionNames.forEach(function(regionName) {
					getAmiId(regionName, owner, name, function(err, amiId) {
						if (err) console.error("Error getting AMI owned by '" + owner + "' and named" + name + " in region '" + regionName + "'");
						else {
							mapping[regionName] = { ID: amiId };
							console.log("Template '" + templatePath + "', mapping '" + mappingKey + "', region " + regionName + ", AMI is " + amiId);
							taskComplete(--tasksCount, templatePath, processedTemplatePath, template, compact);
						}
					});
				});
			});

			var resources = template.Resources;
			fileIncludes.forEach(function(fileInclude) {
				var resourceKey = fileInclude.resourceKey;
				var configKey = fileInclude.configKey;
				var fileKey = fileInclude.fileKey;
				var includePath = fileInclude.includePath;
				console.log("Template '" + templatePath + "', resource '" + resourceKey + "', including '" + includePath + "' as '" + fileKey + "'");
				fs.readFile(includePath, "utf8", function (err, content) {
					if (err) {
						if (err.errno == 34) console.error("File-include " + includePath + " not found");
						else console.error("Error reading file-include " + includePath + ": " + err);
					}
					else {
						resources[resourceKey].Metadata["AWS::CloudFormation::Init"][configKey].files[fileKey].content = content;
						taskComplete(--tasksCount, templatePath, processedTemplatePath, template, compact);
					}
				});
			});
		}
	});
}

function processConfigs(regionName, configPathPatterns, workingDirectory, compact, processedExtension) {
	getRegionNames(regionName, function(err, regionNames) {
		if (err) console.error("Error getting AWS regions: " + err);
		else {
			getConfigPaths(workingDirectory, configPathPatterns, function (err, configPaths) {
				if (err) console.error("Error finding configs matching '" + configPatterns + "': " + err);
				else {
					configPaths.forEach(function(configPath) {
						console.log("Config path '" + configPath + "'...");
						jsonFile.readFile(configPath, function(err, config) {
							if (err) {
								if (err.errno == 34) console.error("Config " + configPath + " not found");
								else console.error("Error reading config " + configPath + ": " + err);
							}
							else {
								var configExtension = path.extname(configPath);
								var configDirectory = path.dirname(configPath);
								var templateName = path.basename(configPath, configExtension);
								var templatePath = path.join(configDirectory, templateName);
								var processedTemplatePath = templatePath + processedExtension;
								processTemplate(config, regionNames, templatePath, compact, processedTemplatePath);
							}
						});
					});
				}
			});
		}
	});
}

module.exports = processConfigs;
<commit_msg>Report if file-include keys are missing<commit_after>"use strict"

var fs = require("fs");
var path = require("path");

var aws = require("aws-sdk");
var glob = require("glob");
var jsonFile = require("jsonfile");
var emptyObject = require("empty-object");
var forEach = require("foreach");

function getEC2Client(regionName) {
	return new aws.EC2( { region: regionName } );
}

function getRegionNames(regionName, callback) {
	getEC2Client(regionName).describeRegions(function(err, result) {
		if (err) callback(err);
		else {
			var names = result.Regions.map(function(o) { return o.RegionName; });
			callback(null, names);
		}
	});
}

function getConfigPaths(pathPatterns, workingDirectory, callback) {
	if (typeof workingDirectory != "string") workingDirectory = process.cwd();
	if (!Array.isArray(pathPatterns) || pathPatterns.length == 0) {
		pathPatterns = [ "*.cfnpp" ];
	}
	pathPatterns.forEach(function(pattern) {
		glob(pattern, function (err, paths) {
			if (err) callback(err);
			else {
				paths = paths.map(function(o) { return path.resolve(workingDirectory, o); } );
				callback(null, paths);
			}
		});
	});
}

function getAmiMappings(config) {
	var mappings = [];
	forEach(config.amiMappings, function(mapping, key) {
		mappings.push({
			"mappingKey": key,
			"owner": mapping.owner || "amazon",
			"name": mapping.name
		});
	});
	return mappings;
}

function getFileIncludes(config, relativeDirectory) {
	var fileIncludes = [];
	forEach(config.fileIncludes, function(fileInclude, resourceKey) {
		forEach(fileInclude, function(fileIncludeConfig, configKey) {
			forEach(fileIncludeConfig, function(fileIncludeName, fileKey){
				fileIncludes.push({
					"resourceKey": resourceKey,
					"configKey": configKey,
					"fileKey": fileKey,
					"includePath": path.resolve(relativeDirectory, fileIncludeName)
				})
			})
		});
	});
	return fileIncludes;
}

function getAmiId(regionName, owner, name, callback) {
	getEC2Client(regionName).describeImages({
		Owners: [ owner ],
		Filters: [ { Name: "name", Values: [ name ] } ]
	}, function(err, data) {
		if (err) callback(err);
		else {
			var images = data.Images.sort(function(a,b) { f
				var a = a.Name;
				var b = b.Name;
				return (a > b) ? 1 : ((a < b) ? -1 : 0);
			});
			if (images.length == 0) callback(new Error("AMI owned by '" + owner + "' and named '" + name + "' not found"));
			else callback(null, images[0].ImageId);
		}
	});
}

function taskComplete(tasksCount, templatePath, processedTemplatePath, template, compact) {
	if (tasksCount == 0) {
		jsonFile.spaces = compact ? 0 : 2;
		jsonFile.writeFile(processedTemplatePath, template, function(err) {
			if (err) console.error("Error saving template '" + processedTemplatePath + "': " + err);
			else console.log("Template '" + templatePath + "' pre-processed to '" + processedTemplatePath + "'");
		});
	}
}

function processTemplate(config, regionNames, templatePath, compact, processedTemplatePath) {
	console.log("Template path is " + templatePath);
	jsonFile.readFile(templatePath, function (err, template) {
		//console.log("Tenplate is " + JSON.stringify(template));
		if (err) console.error("Error reading '" + templatePath + "': " + err);
		else {
			var amiMappings = getAmiMappings(config);
			var amiMappingsCount = amiMappings.length;

			var fileIncludes = getFileIncludes(config, path.dirname(templatePath));
			var fileIncludesCount = fileIncludes.length;

			console.log("Template '" + templatePath + "': " + amiMappingsCount + " AMI mappings and " + fileIncludesCount + " file-includes");

			var tasksCount = (amiMappingsCount * regionNames.length) + fileIncludesCount;

			var mappings = template.Mappings;
			amiMappings.forEach(function(amiMappingTask) {
				var mappingKey = amiMappingTask.mappingKey;
				var owner = amiMappingTask.owner;
				var name = amiMappingTask.name;
				console.log("Template '" + templatePath + "', mapping '" + mappingKey + "': searching for last AMI owned by '" + owner + "' and named '" + name + "'");
				var mapping = mappings[mappingKey];
				emptyObject(mapping);
				regionNames.forEach(function(regionName) {
					getAmiId(regionName, owner, name, function(err, amiId) {
						if (err) console.error("Error getting AMI owned by '" + owner + "' and named" + name + " in region '" + regionName + "'");
						else {
							mapping[regionName] = { ID: amiId };
							console.log("Template '" + templatePath + "', mapping '" + mappingKey + "', region " + regionName + ", AMI is " + amiId);
							taskComplete(--tasksCount, templatePath, processedTemplatePath, template, compact);
						}
					});
				});
			});

			var resources = template.Resources;
			fileIncludes.forEach(function(fileInclude) {
				var resourceKey = fileInclude.resourceKey;
				var configKey = fileInclude.configKey;
				var fileKey = fileInclude.fileKey;
				var includePath = fileInclude.includePath;
				console.log("Template '" + templatePath + "', resource '" + resourceKey + "', including '" + includePath + "' as '" + fileKey + "'");
				fs.readFile(includePath, "utf8", function (err, content) {
					if (err) {
						if (err.errno == 34) console.error("File " + includePath + " not found");
						else console.error("Error reading file " + includePath + ": " + err);
					}
					else {
						var config = resources[resourceKey].Metadata["AWS::CloudFormation::Init"][configKey];
						if (config) {
							var file = config.files[fileKey];
							if (file) {
								file.content = content;
							}
							else console.error("There is no file named '" + fileKey + "' in config '" + configKey + "'");
						}
						else console.error("There is no config named '" + configKey + "'");
						taskComplete(--tasksCount, templatePath, processedTemplatePath, template, compact);
					}
				});
			});
		}
	});
}

function processConfigs(regionName, configPathPatterns, workingDirectory, compact, processedExtension) {
	getRegionNames(regionName, function(err, regionNames) {
		if (err) console.error("Error getting AWS regions: " + err);
		else {
			getConfigPaths(workingDirectory, configPathPatterns, function (err, configPaths) {
				if (err) console.error("Error finding configs matching '" + configPatterns + "': " + err);
				else {
					configPaths.forEach(function(configPath) {
						console.log("Config path '" + configPath + "'...");
						jsonFile.readFile(configPath, function(err, config) {
							if (err) {
								if (err.errno == 34) console.error("Config " + configPath + " not found");
								else console.error("Error reading config " + configPath + ": " + err);
							}
							else {
								var configExtension = path.extname(configPath);
								var configDirectory = path.dirname(configPath);
								var templateName = path.basename(configPath, configExtension);
								var templatePath = path.join(configDirectory, templateName);
								var processedTemplatePath = templatePath + processedExtension;
								processTemplate(config, regionNames, templatePath, compact, processedTemplatePath);
							}
						});
					});
				}
			});
		}
	});
}

module.exports = processConfigs;
<|endoftext|>