<commit_before>(function(DOM, BASE, VK_SPACE, VK_TAB, VK_ENTER, VK_ESCAPE, VK_BACKSPACE, VK_DELETE, VK_CONTROL) {
    "use strict";

    var HTML = DOM.get("documentElement"),
        ampm = (pos, neg) => HTML.lang === "en-US" ? pos : neg,
        formatISODate = (value) => value.toISOString().split("T")[0],
        PICKER_TEMPLATE = DOM.create(DOM.emmet(`div.${BASE}-calendar>(p.${BASE}-calendar-header>a[unselectable=on]*2+time[is=local-time data-format='MMMM yyyy' aria-hidden=true unselectable=on].${BASE}-calendar-caption)`)),
        DAYS_TEMPLATE = DOM.create(DOM.emmet(`table[aria-hidden=true].${BASE}-calendar-days>(thead>(tr>th[unselectable=on]*7>time[is=local-time data-format=E]))+(tbody.${BASE}-calendar-body*2>tr*6>td*7)`)),
        MONTHS_TEMPLATE = DOM.create(DOM.emmet(`table[aria-hidden=true].${BASE}-calendar-months>tbody>tr*3>td*4>time[is=local-time data-format=MMM])`)),
        LABEL_TEMPLATE = DOM.create(DOM.emmet(`time[is=local-time aria-hidden=true].${BASE}-value`)),
        readDateRange = (el) => ["min", "max"].map((x) => new Date(el.get(x) || ""));

    MONTHS_TEMPLATE.findAll("time").forEach((time, index) => {
        time.set("datetime", new Date(2001, index, 9).toISOString());
    });

    DAYS_TEMPLATE.findAll("time").forEach((time, index) => {
        time.set("datetime", new Date(ampm(2001, 2002), 0, index).toISOString());
    });

    PICKER_TEMPLATE.append(DAYS_TEMPLATE).append(MONTHS_TEMPLATE);

    DOM.extend("input[type=date]", {
        constructor() {
            if (this._isNative()) return false;

            var picker = PICKER_TEMPLATE.clone(true),
                label = LABEL_TEMPLATE.clone(true),
                calenderDays = picker.findAll(`.${BASE}-calendar-body`),
                calendarMonths = picker.find(`.${BASE}-calendar-months`),
                calendarCaption = picker.find(`.${BASE}-calendar-caption`),
                changeValue = this._changeValue.bind(this, calendarCaption, calendarMonths, calenderDays, picker);

            this
                // sync picker visibility on focus/blur
                .on(["focus", "click"], this._focusPicker.bind(this, picker))
                .on("blur", this._blurPicker.bind(this, picker))
                .on("change", this._syncDateValue.bind(this, label))
                .before(picker.hide(), label);

            label
                .set("data-format", this.get("data-format") || "E, dd MMM yyyy")
                .css(this.css(["color", "width", "font", "padding", "text-align", "border-width", "box-sizing"]))
                .on("click", this._clickLabel.bind(this));

            calenderDays[1].hide().remove();
            calendarCaption.on("click", this._clickPickerCaption.bind(this, picker, calendarCaption, changeValue));

            // handle arrow keys, esc etc.
            this
                .on("keydown", ["which"], this._keydownPicker.bind(this, picker, calendarCaption))
                .watch("value", changeValue);

            this.closest("form").on("reset", this._resetForm.bind(this));
            // trigger watchers to build the calendar
            changeValue(this.get("defaultValue"));

            picker
                .on("mousedown", ["target"], this._clickPicker.bind(this, picker, calendarMonths))
                .watch("aria-hidden", this._changePickerVisibility.bind(this, picker, calendarCaption))
                .css("z-index", 1 + (this.css("z-index") | 0))
                .hide(); // hide calendar to trigger show animation properly later

            // hide original input text
            // IE8 doesn't suport color:transparent - use background-color instead
            this.css("color", document.addEventListener ? "transparent" : this.css("background-color"));
            // display calendar for autofocused elements
            if (this.matches(":focus")) picker.show();
        },
        _isNative() {
            var polyfillType = this.get("data-polyfill"),
                deviceType = "orientation" in window ? "mobile" : "desktop";

            if (polyfillType === "none") return true;

            if (!polyfillType || polyfillType === deviceType && polyfillType !== "all") {
                // use a stronger type support detection that handles old WebKit browsers:
                // http://www.quirksmode.org/blog/archives/2015/03/better_modern_i.html
                if (this[0].type === "date") return true;

                var invalidValue = this.value("_").value();
                // if browser allows invalid value then it doesn't support the feature
                return invalidValue !== "_";
            } else {
                // remove native control
                this.set("type", "text");
                // force applying the polyfill
                return false;
            }
        },
        _changeValue(caption, calendarMonths, calenderDays, picker, value, prevValue) {
            // #47: do not proceed if animation is in progress still
            if (calenderDays.every((days) => picker.contains(days))) return false;

            var year, month, date, iterDate;

            value = new Date(value);

            if (!value.getTime()) {
                value = new Date();
            }

            month = value.getUTCMonth();
            date = value.getUTCDate();
            year = value.getUTCFullYear();
            // update calendar caption
            caption.set("datetime", new Date(year, month).toISOString());
            // update calendar content
            iterDate = new Date(Date.UTC(year, month, 1));

            var range = readDateRange(this);

            if (picker.get("aria-expanded") === "true") {
                calendarMonths.findAll("td").forEach((day, index) => {
                    iterDate.setUTCMonth(index);

                    var mDiff = month - iterDate.getUTCMonth(),
                        className = `${BASE}-calendar-`;

                    if (iterDate < range[0] || iterDate > range[1]) {
                        className += "out";
                    } else if (!mDiff) {
                        className += "today";
                    } else {
                        className = "";
                    }

                    day.set("class", className);
                });
            } else {
                // move to beginning of current month week
                iterDate.setUTCDate(iterDate.getUTCDate() - iterDate.getUTCDay() - ampm(1, 0));

                prevValue = new Date(prevValue);

                var delta = value.getUTCMonth() - prevValue.getUTCMonth() + 100 * (value.getUTCFullYear() - prevValue.getUTCFullYear());
                var currenDays = calenderDays[picker.contains(calenderDays[0]) ? 0 : 1];
                var targetDays = delta ? calenderDays[calenderDays[0] === currenDays ? 1 : 0] : currenDays;
                // update days
                targetDays.findAll("td").forEach((day) => {
                    iterDate.setUTCDate(iterDate.getUTCDate() + 1);

                    var mDiff = month - iterDate.getUTCMonth(),
                        className = `${BASE}-calendar-`;

                    if (year !== iterDate.getUTCFullYear()) mDiff *= -1;

                    if (iterDate < range[0] || iterDate > range[1]) {
                        className += "out";
                    } else if (mDiff > 0) {
                        className += "past";
                    } else if (mDiff < 0) {
                        className += "future";
                    } else if (date === iterDate.getUTCDate()) {
                        className += "today";
                    } else {
                        className = "";
                    }

                    day
                        .set("class", className)
                        .data("ts", iterDate.getTime())
                        .value(iterDate.getUTCDate());
                });

                if (delta) {
                    currenDays[delta > 0 ? "after" : "before"](targetDays);
                    currenDays.hide(() => { currenDays.remove() });
                    targetDays.show();
                }
            }

            // trigger event manually to notify about changes
            this.fire("change");
        },
        _syncDateValue(time) {
            time.set("datetime", this.value());
        },
        _clickPicker(picker, calendarMonths, target) {
            var targetDate;

            if (target.matches("a")) {
                targetDate = new Date(this.value());

                if (!targetDate.getTime()) targetDate = new Date();

                var sign = target.next("a")[0] ? -1 : 1;

                if (picker.get("aria-expanded") === "true") {
                    targetDate.setUTCFullYear(targetDate.getUTCFullYear() + sign);
                } else {
                    targetDate.setUTCMonth(targetDate.getUTCMonth() + sign);
                }
            } else if (calendarMonths.contains(target)) {
                target = target.closest("time");

                targetDate = new Date(this.value());
                targetDate.setUTCMonth(new Date(target.get("datetime")).getUTCMonth());

                picker.hide();
            } else if (target.matches("td")) {
                targetDate = target.data("ts");

                if (targetDate) {
                    targetDate = new Date(targetDate);
                    picker.hide();
                }
            }

            if (targetDate != null) {
                var range = readDateRange(this);

                if (targetDate < range[0]) {
                    targetDate = range[0];
                } else if (targetDate > range[1]) {
                    targetDate = range[1];
                }

                this.value(formatISODate(targetDate));
            }
            // prevent input from loosing focus
            return false;
        },
        _keydownPicker(picker, calendarCaption, which) {
            var delta, currentDate;
            // ENTER key should submit form if calendar is hidden
            if (picker.matches(":hidden") && which === VK_ENTER) return true;

            if (which === VK_SPACE) {
                picker.toggle(); // SPACE key toggles calendar visibility
            } else if (which === VK_ESCAPE || which === VK_TAB || which === VK_ENTER) {
                picker.hide(); // ESC, TAB or ENTER keys hide calendar
            } else if (which === VK_BACKSPACE || which === VK_DELETE) {
                this.empty(); // BACKSPACE, DELETE clear value
            } else if (which === VK_CONTROL) {
                calendarCaption.fire("click"); // CONTROL toggles calendar mode
            } else {
                currentDate = new Date(this.value());

                if (!currentDate.getTime()) currentDate = new Date();

                if (which === 74 || which === 40) { delta = 7; }
                else if (which === 75 || which === 38) { delta = -7; }
                else if (which === 76 || which === 39) { delta = 1; }
                else if (which === 72 || which === 37) { delta = -1; }

                if (delta) {
                    var expanded = picker.get("aria-expanded") === "true";

                    if (expanded && (which === 40 || which === 38)) {
                        currentDate.setUTCMonth(currentDate.getUTCMonth() + (delta > 0 ? 4 : -4));
                    } else if (expanded && (which === 37 || which === 39)) {
                        currentDate.setUTCMonth(currentDate.getUTCMonth() + (delta > 0 ? 1 : -1));
                    } else {
                        currentDate.setUTCDate(currentDate.getUTCDate() + delta);
                    }

                    var range = readDateRange(this);

                    if (!(currentDate < range[0] || currentDate > range[1])) {
                        this.value(formatISODate(currentDate));
                    }
                }
            }
            // prevent default action except if it was TAB so
            // do not allow to change the value manually
            return which === VK_TAB;
        },
        _blurPicker(picker) {
            picker.hide();
        },
        _focusPicker(picker) {
            if (this.get("readonly")) return false;

            var offset = this.offset();
            var pickerOffset = picker.offset();
            var marginTop = offset.height;

            // #3: move calendar to the top when passing cross browser window bounds
            if (HTML.clientHeight < offset.bottom + pickerOffset.height) {
                marginTop = -pickerOffset.height;
            }

            // always recalculate picker top position
            picker.css("margin-top", marginTop).show();

            // use the trick below to reset text selection on focus
            setTimeout(() => {
                var node = this[0];

                if ("selectionStart" in node) {
                    node.selectionStart = 0;
                    node.selectionEnd = 0;
                } else {
                    var inputRange = node.createTextRange();

                    inputRange.moveStart("character", 0);
                    inputRange.collapse();
                    inputRange.moveEnd("character", 0);
                    inputRange.select();
                }
            }, 0);
        },
        _clickPickerCaption(picker, calendarCaption, changeValue) {
            if (picker.get("aria-expanded") === "true") {
                calendarCaption.set("data-format", "MMMM yyyy");

                picker.set("aria-expanded", "false");
            } else {
                calendarCaption.set("data-format", "yyyy");

                picker.set("aria-expanded", "true");
            }

            changeValue(this.value());
        },
        _changePickerVisibility(picker, calendarCaption, hidden) {
            if (hidden !== "true") {
                if (picker.get("aria-expanded") === "true") {
                    // restore picker state
                    calendarCaption.fire("click");
                }
            }
        },
        _clickLabel() {
            this.fire("focus");
        },
        _resetForm() {
            this.value(this.get("defaultValue"));
        }
    });
}(window.DOM, "btr-dateinput", 32, 9, 13, 27, 8, 46, 17));
<commit_msg>simplify code<commit_after>(function(DOM, BASE, VK_SPACE, VK_TAB, VK_ENTER, VK_ESCAPE, VK_BACKSPACE, VK_DELETE, VK_CONTROL) {
    "use strict";

    var HTML = DOM.get("documentElement"),
        ampm = (pos, neg) => HTML.lang === "en-US" ? pos : neg,
        formatISODate = (value) => value.toISOString().split("T")[0],
        PICKER_TEMPLATE = DOM.create(DOM.emmet(`div.${BASE}-calendar>(p.${BASE}-calendar-header>a[unselectable=on]*2+time[is=local-time data-format='MMMM yyyy' aria-hidden=true unselectable=on].${BASE}-calendar-caption)`)),
        DAYS_TEMPLATE = DOM.create(DOM.emmet(`table[aria-hidden=true].${BASE}-calendar-days>(thead>(tr>th[unselectable=on]*7>time[is=local-time data-format=E]))+(tbody.${BASE}-calendar-body*2>tr*6>td*7)`)),
        MONTHS_TEMPLATE = DOM.create(DOM.emmet(`table[aria-hidden=true].${BASE}-calendar-months>tbody>tr*3>td*4>time[is=local-time data-format=MMM])`)),
        LABEL_TEMPLATE = DOM.create(DOM.emmet(`time[is=local-time aria-hidden=true].${BASE}-value`)),
        readDateRange = (el) => ["min", "max"].map((x) => new Date(el.get(x) || ""));

    MONTHS_TEMPLATE.findAll("time").forEach((time, index) => {
        time.set("datetime", new Date(2001, index, 9).toISOString());
    });

    DAYS_TEMPLATE.findAll("time").forEach((time, index) => {
        time.set("datetime", new Date(ampm(2001, 2002), 0, index).toISOString());
    });

    PICKER_TEMPLATE.append(DAYS_TEMPLATE).append(MONTHS_TEMPLATE);

    DOM.extend("input[type=date]", {
        constructor() {
            if (this._isNative()) return false;

            var picker = PICKER_TEMPLATE.clone(true),
                label = LABEL_TEMPLATE.clone(true),
                calenderDays = picker.findAll(`.${BASE}-calendar-body`),
                calendarMonths = picker.find(`.${BASE}-calendar-months`),
                calendarCaption = picker.find(`.${BASE}-calendar-caption`),
                changeValue = this._changeValue.bind(this, calendarCaption, calendarMonths, calenderDays, picker);

            this
                // sync picker visibility on focus/blur
                .on(["focus", "click"], this._focusPicker.bind(this, picker))
                .on("blur", this._blurPicker.bind(this, picker))
                .on("change", this._syncDateValue.bind(this, label))
                .before(picker.hide(), label);

            label
                .set("data-format", this.get("data-format") || "E, dd MMM yyyy")
                .css(this.css(["color", "width", "font", "padding", "text-align", "border-width", "box-sizing"]))
                .on("click", this._clickLabel.bind(this));

            calenderDays[1].hide().remove();
            calendarCaption.on("click", this._clickPickerCaption.bind(this, picker, calendarCaption, changeValue));

            // handle arrow keys, esc etc.
            this
                .on("keydown", ["which"], this._keydownPicker.bind(this, picker, calendarCaption))
                .watch("value", changeValue);

            this.closest("form").on("reset", this._resetForm.bind(this));
            // trigger watchers to build the calendar
            changeValue(this.get("defaultValue"));

            picker
                .on("mousedown", ["target"], this._clickPicker.bind(this, picker, calendarMonths))
                .css("z-index", 1 + (this.css("z-index") | 0))
                .hide(); // hide calendar to trigger show animation properly later

            // hide original input text
            // IE8 doesn't suport color:transparent - use background-color instead
            this.css("color", document.addEventListener ? "transparent" : this.css("background-color"));
            // display calendar for autofocused elements
            if (this.matches(":focus")) picker.show();
        },
        _isNative() {
            var polyfillType = this.get("data-polyfill"),
                deviceType = "orientation" in window ? "mobile" : "desktop";

            if (polyfillType === "none") return true;

            if (!polyfillType || polyfillType === deviceType && polyfillType !== "all") {
                // use a stronger type support detection that handles old WebKit browsers:
                // http://www.quirksmode.org/blog/archives/2015/03/better_modern_i.html
                if (this[0].type === "date") return true;

                var invalidValue = this.value("_").value();
                // if browser allows invalid value then it doesn't support the feature
                return invalidValue !== "_";
            } else {
                // remove native control
                this.set("type", "text");
                // force applying the polyfill
                return false;
            }
        },
        _changeValue(caption, calendarMonths, calenderDays, picker, value, prevValue) {
            // #47: do not proceed if animation is in progress still
            if (calenderDays.every((days) => picker.contains(days))) return false;

            var year, month, date, iterDate;

            value = new Date(value);

            if (!value.getTime()) {
                value = new Date();
            }

            month = value.getUTCMonth();
            date = value.getUTCDate();
            year = value.getUTCFullYear();
            // update calendar caption
            caption.set("datetime", new Date(year, month).toISOString());
            // update calendar content
            iterDate = new Date(Date.UTC(year, month, 1));

            var range = readDateRange(this);

            if (picker.get("aria-expanded") === "true") {
                calendarMonths.findAll("td").forEach((day, index) => {
                    iterDate.setUTCMonth(index);

                    var mDiff = month - iterDate.getUTCMonth(),
                        className = `${BASE}-calendar-`;

                    if (iterDate < range[0] || iterDate > range[1]) {
                        className += "out";
                    } else if (!mDiff) {
                        className += "today";
                    } else {
                        className = "";
                    }

                    day.set("class", className);
                });
            } else {
                // move to beginning of current month week
                iterDate.setUTCDate(iterDate.getUTCDate() - iterDate.getUTCDay() - ampm(1, 0));

                prevValue = new Date(prevValue);

                var delta = value.getUTCMonth() - prevValue.getUTCMonth() + 100 * (value.getUTCFullYear() - prevValue.getUTCFullYear());
                var currenDays = calenderDays[picker.contains(calenderDays[0]) ? 0 : 1];
                var targetDays = delta ? calenderDays[calenderDays[0] === currenDays ? 1 : 0] : currenDays;
                // update days
                targetDays.findAll("td").forEach((day) => {
                    iterDate.setUTCDate(iterDate.getUTCDate() + 1);

                    var mDiff = month - iterDate.getUTCMonth(),
                        className = `${BASE}-calendar-`;

                    if (year !== iterDate.getUTCFullYear()) mDiff *= -1;

                    if (iterDate < range[0] || iterDate > range[1]) {
                        className += "out";
                    } else if (mDiff > 0) {
                        className += "past";
                    } else if (mDiff < 0) {
                        className += "future";
                    } else if (date === iterDate.getUTCDate()) {
                        className += "today";
                    } else {
                        className = "";
                    }

                    day
                        .set("class", className)
                        .data("ts", iterDate.getTime())
                        .value(iterDate.getUTCDate());
                });

                if (delta) {
                    currenDays[delta > 0 ? "after" : "before"](targetDays);
                    currenDays.hide(() => { currenDays.remove() });
                    targetDays.show();
                }
            }

            // trigger event manually to notify about changes
            this.fire("change");
        },
        _syncDateValue(time) {
            time.set("datetime", this.value());
        },
        _clickPicker(picker, calendarMonths, target) {
            var targetDate;

            if (target.matches("a")) {
                targetDate = new Date(this.value());

                if (!targetDate.getTime()) targetDate = new Date();

                var sign = target.next("a")[0] ? -1 : 1;

                if (picker.get("aria-expanded") === "true") {
                    targetDate.setUTCFullYear(targetDate.getUTCFullYear() + sign);
                } else {
                    targetDate.setUTCMonth(targetDate.getUTCMonth() + sign);
                }
            } else if (calendarMonths.contains(target)) {
                target = target.closest("time");

                targetDate = new Date(this.value());
                targetDate.setUTCMonth(new Date(target.get("datetime")).getUTCMonth());

                picker.hide();
            } else if (target.matches("td")) {
                targetDate = target.data("ts");

                if (targetDate) {
                    targetDate = new Date(targetDate);
                    picker.hide();
                }
            }

            if (targetDate != null) {
                var range = readDateRange(this);

                if (targetDate < range[0]) {
                    targetDate = range[0];
                } else if (targetDate > range[1]) {
                    targetDate = range[1];
                }

                this.value(formatISODate(targetDate));
            }
            // prevent input from loosing focus
            return false;
        },
        _keydownPicker(picker, calendarCaption, which) {
            var delta, currentDate;
            // ENTER key should submit form if calendar is hidden
            if (picker.matches(":hidden") && which === VK_ENTER) return true;

            if (which === VK_SPACE) {
                picker.toggle(); // SPACE key toggles calendar visibility
            } else if (which === VK_ESCAPE || which === VK_TAB || which === VK_ENTER) {
                picker.hide(); // ESC, TAB or ENTER keys hide calendar
            } else if (which === VK_BACKSPACE || which === VK_DELETE) {
                this.empty(); // BACKSPACE, DELETE clear value
            } else if (which === VK_CONTROL) {
                calendarCaption.fire("click"); // CONTROL toggles calendar mode
            } else {
                currentDate = new Date(this.value());

                if (!currentDate.getTime()) currentDate = new Date();

                if (which === 74 || which === 40) { delta = 7; }
                else if (which === 75 || which === 38) { delta = -7; }
                else if (which === 76 || which === 39) { delta = 1; }
                else if (which === 72 || which === 37) { delta = -1; }

                if (delta) {
                    var expanded = picker.get("aria-expanded") === "true";

                    if (expanded && (which === 40 || which === 38)) {
                        currentDate.setUTCMonth(currentDate.getUTCMonth() + (delta > 0 ? 4 : -4));
                    } else if (expanded && (which === 37 || which === 39)) {
                        currentDate.setUTCMonth(currentDate.getUTCMonth() + (delta > 0 ? 1 : -1));
                    } else {
                        currentDate.setUTCDate(currentDate.getUTCDate() + delta);
                    }

                    var range = readDateRange(this);

                    if (!(currentDate < range[0] || currentDate > range[1])) {
                        this.value(formatISODate(currentDate));
                    }
                }
            }
            // prevent default action except if it was TAB so
            // do not allow to change the value manually
            return which === VK_TAB;
        },
        _blurPicker(picker) {
            picker.hide();
        },
        _focusPicker(picker) {
            if (this.get("readonly")) return false;

            var offset = this.offset();
            var pickerOffset = picker.offset();
            var marginTop = offset.height;

            // #3: move calendar to the top when passing cross browser window bounds
            if (HTML.clientHeight < offset.bottom + pickerOffset.height) {
                marginTop = -pickerOffset.height;
            }

            picker
                // always recalculate picker top position
                .css("margin-top", marginTop)
                // always reset picker mode to default
                .set("aria-expanded", "false")
                .show();

            // use the trick below to reset text selection on focus
            setTimeout(() => {
                var node = this[0];

                if ("selectionStart" in node) {
                    node.selectionStart = 0;
                    node.selectionEnd = 0;
                } else {
                    var inputRange = node.createTextRange();

                    inputRange.moveStart("character", 0);
                    inputRange.collapse();
                    inputRange.moveEnd("character", 0);
                    inputRange.select();
                }
            }, 0);
        },
        _clickPickerCaption(picker, calendarCaption, changeValue) {
            if (picker.get("aria-expanded") === "true") {
                calendarCaption.set("data-format", "MMMM yyyy");

                picker.set("aria-expanded", "false");
            } else {
                calendarCaption.set("data-format", "yyyy");

                picker.set("aria-expanded", "true");
            }

            changeValue(this.value());
        },
        _clickLabel() {
            this.fire("focus");
        },
        _resetForm() {
            this.value(this.get("defaultValue"));
        }
    });
}(window.DOM, "btr-dateinput", 32, 9, 13, 27, 8, 46, 17));
<|endoftext|>


<commit_before>'use strict'

/**
 * Dependencies
 */
const _ = require('lodash')
const path = require('path')
const injector = require('./injector')

/**
 * Symbols
 */
let init = Symbol()

/**
 * Plugin
 *
 * Instances of Plugin expose an API which can be accessed by the developer to
 * define plugins, along with methods used by the Registry to manage plugin lifecycle.
 *
 * The developer API can be used to:
 *
 *    - register node modules as dependencies on the injector
 *    - include files in the plugin definition for managing large plugins
 *    - define dependencies using factory methods
 *    - alias dependencies
 *    - create adapters to determine which implementation of an interface to
 *      use at runtime
 *    - access dependencies for mutation without creating new dependencies
 *    - extend the plugin API
 *    - register callbacks for managing plugin lifecycle
 *
 */
class Plugin {

  /**
   * Constructor
   */
  constructor (name, metadata) {
    this.name = name
    this.metadata = metadata
  }

  /**
   * Require
   *
   * Adds dependencies to the injector by loading node modules. Accepts a string,
   * array or object. By passing an object you can alias the package name.
   *
   * Example
   *
   *    sunstone.plugin(<NAME>, <METADATA>)
   *      .require('express')
   *
   *      .require([
   *        'crypto',
   *        'ioredis'
   *      ])
   *
   *      .require({
   *        '_': 'lodash',
   *        'fs': 'fs-extra',
   *        'myLibrary': './myLibrary'
   *      })
   *
   * TODO
   * - path based requires are currently relative to this file.
   *   it might be better to make them relative to process.cwd()
   *   * require.resolve() can allow us to resolve the full path
   *     of a particular file, however in order to do so, it needs
   *     to be called from within the file itself.
   */
  require (modules) {
    if (typeof modules === 'string') {
      modules = { modules }
    }

    if (Array.isArray(modules)) {
      modules = _.zipObject(modules, modules)
    }

    Object.keys(modules).forEach((key) => {
      this.module(key, function () {
        return require(modules[key])
      })
    })

    return this
  }

  /**
   * Include
   *
   * The bootstrapping process searches through the plugins directory looking for
   * plugins to load. To make a new plugin you simply create a sub-directory with an
   * index.js file. Additional files in the directory are incorporated using the
   * include method. This allows developers to separate out one plugin into an
   * arbitrary number of files.
   *
   * Example:
   *
   *    // index.js
   *    'use strict'
   *
   *    module.exports = function (sunstone) {
   *      sunstone.plugin('MyResource', {
   *        version: '0.0.1',
   *        dependencies: {
   *          'Server': '0.0.1'
   *        }
   *      })
   *      .initializer(function (plugin) {
   *        .include(__dirname, 'other')
   *        .include(__dirname, 'yetanother')
   *      })
   *    }
   *
   *    // other.js
   *    'use strict'
   *
   *    module.exports = function (plugin) {
   *
   *      plugin
   *        .factory('MyModel', function (a, b) {
   *          // ...
   *        })
   *        .router('MyModelRouter', function (MyModel) {
   *          // ...
   *        })
   *
   *    }
   */
  include () {
    let segments = Array.prototype.slice.call(arguments, this.include.length)
    let filepath = path.join.apply(null, segments)
    require(filepath)(this)
    return this
  }

  /**
   * Module
   *
   * This is used internally by `plugin.require()` to register modules with
   * the type "module". This is important to maintain a distinction between
   * components provided by plugins defined in the host (or extending applications)
   * and components that originate from node modules.
   */
  module (name, fn) {
    injector.register({
      name,
      type: 'module',
      plugin: this.name,
      fn
    })
  }

  /**
   * Factory
   *
   * The factory method registers a new dependency on the injector, validates it, and
   * determines which other dependencies it requires.
   *
   * The first argument is the name of the new dependency and the second argument is a
   * function that returns the value of the dependency. However, this dependency is not
   * invoked at the time the dependency is registered.
   *
   * Getting a dependency from the Injector invokes the function and stores the return
   * value.
   *
   * Example:
   *
   *    plugin
   *      .factory('one', function () {
   *        return 1
   *      })
   *      .factory('two', function () {
   *        return 2
   *      })
   *      .factory('oneplustwo', function (one, two) {
   *        return one + two
   *      })
   *
   */
  factory (name, fn) {
    injector.register({
      name,
      type: 'factory',
      plugin: this.name,
      fn
    })

    return this
  }

  /**
   * Adapter
   *
   * Create factories that determine which implementation
   * to use at injection time.
   *
   * Example:
   *
   *   sunstone.plugin(<NAME>, <METADATA>)
   *     .factory('RedisResource', function () {})
   *     .factory('MongoResource', function () {})
   *     .adapter('Resource', function (injector, settings) {
   *        // where settings.property is 'RedisResource' or 'MongoResource'
   *        return injector.get(settings.property)
   *     })
   *     .factory('User', function (Resource) {})
   *
   */
  adapter (name, fn) {
    injector.register({
      name,
      type: 'adapter',
      plugin: this.name,
      fn
    })

    return this
  }

  /**
   * Alias
   *
   * Alias creates a reference to another item on the injector
   * within its own injector object. When the alias is injected
   * through the use of the injector.get() method it calls the
   * aliased dependency and creates a reference to the instance
   * in the dependency.value field.
   *
   * Example:
   *
   *   sunstone.plugin(<NAME>, <METADATA)
   *   .factory('myDependency', function () {
   *     // ...
   *   })
   *
   *   .alias('myAlias', 'myDependency')
   *
   */
  alias (alias, name) {
    injector.register({
      name: alias,
      type: 'alias',
      plugin: this.name,
      fn: () => {
        return injector.get(name)
      }
    })

    return this
  }

  /**
   * Extension
   *
   * The idea of an extension is that you can access some component
   * to use it's API without registering anything on the injector.
   *
   * This is useful for things like modifying a model's schema or
   * registering event handlers on an event emitter.
   *
   * Example 1:
   *
   *   sunstone.plugin('Default API', <METADATA>)
   *     .factory('User', function (Resource) {
   *       class User extends Resource {
   *         static get schema () {
   *           return Object.assign({}, super.schema, {
   *             name: { type: 'string' },
   *             email: { type: 'string', format: 'email' }
   *           })
   *         }
   *       }
   *
   *       return User
   *     })
   *
   *   sunstone.plugin('My Project', <METADATA>)
   *     .extension('UserExtension', function (User) {
   *       User.extendSchema({
   *         domainSpecificAttribute: { type: 'whatever', ... }
   *       })
   *     })
   *
   * Example 2:
   *
   *   sunstone.plugin(<NAME>, <METADATA>)
   *     .factory('emitter', function () {
   *        return new EventEmitter()
   *     })
   *
   *   sunstone.plugin('My Project', <METADATA>)
   *     .extension('CustomEventHandlers', function (emitter) {
   *        emitter.on('ready', function (event) {
   *          // do something
   *        })
   *     })
   */
  extension (name, fn) {
    injector.register({
      name,
      type: 'extension',
      plugin: this.name,
      fn
    })

    return this
  }

  /**
   * Assembler
   *
   * This can be used to define new types of components. For example, the core
   * framework probably doesn't need any knowledge of Express routers, but if you
   * wanted to define a specialized factory registrar for routers, you could do it
   * like so:
   *
   *    sunstone.plugin('server', {
   *      version: '0.0.0'
   *    })
   *    .assembler('router', function (injector) {
   *      let plugin = this
   *      return function (name, factory) {
   *        injector.register({
   *          name,
   *          type: 'router',
   *          plugin: plugin.name,
   *          factory
   *        })
   *      })
   *    })
   *
   * This makes a new dependency registrar called 'router' that can be used thusly:
   *
   *    sunstone.plugin('other', {
   *      version: '0.0.0'
   *    })
   *    .router('SomeRouter', function (Router, SomeResource) {
   *      let router = new Router()
   *
   *      router.get('endpoint', function () {
   *        SomeResource
   *          .list(req.query)
   *          .then(function (results) {
   *            res.json(results)
   *          })
   *          .catch(error => next(error))
   *      })
   *
   *      return router
   *    })
   *
   * The dependency inject can then be queried by this new "type" value.
   *
   *    injector.find({ type: 'router' })
   *
   * TODO
   * - there should possibly be a way to create a starter method automatically for
   *   an assembler to save that boilerplate
   */
  assembler (name, fn) {
    this.constructor.prototype[name] = fn(injector, this)
    return this
  }

  /**
   * Lifecycle Management
   *
   * These methods are used to register lifecycle methods that will be called
   * by the plugin manager
   */

  /**
   * Initializer
   *
   * Example:
   *
   *    sunstone.
   */
  initializer (callback) {
    this[init] = callback
    return this
  }

  /**
   * Initialize
   */
  initialize () {
    let fn = this[init]

    if (fn) {
      fn(this)
    }

    return this
  }

  /**
   * Starter
   *
   * Example:
   *
   *    sunstone.plugin(<NAME>, <METADATA>)
   *      .initializer(function (plugin) {
   *        plugin.starter(function (injector, server) {
   *          injector
   *            .find({ plugin: this.name, type: 'router' })
   *            .values()
   *            .forEach(router => {
   *              router.mount(server)
   *            })
   *        })
   *      })
   */
  starter (fn) {
    injector.register({
      name: `${this.name}:starter`,
      type: 'callback',
      plugin: this.name,
      fn
    })

    return this
  }

  /**
   * Start
   */
  start () {
    injector.invoke(`${this.name}:starter`)
    return this
  }

  /**
   * Stopper
   */
  stopper (fn) {
    injector.register({
      name: `${this.name}:stopper`,
      type: 'callback',
      plugin: this.name,
      fn
    })

    return this
  }

  /**
   * Stop
   */
  stop () {
    injector.invoke(`${this.name}:stopper`)
    return this
  }

}

/**
 * Exports
 */
module.exports = Plugin
<commit_msg>feat(Plugin): automate path resolution in plugin.include using callsite<commit_after>'use strict'

/**
 * Dependencies
 */
const _ = require('lodash')
const path = require('path')
const injector = require('./injector')
const callsite = require('callsite')

/**
 * Symbols
 */
let init = Symbol()

/**
 * Plugin
 *
 * Instances of Plugin expose an API which can be accessed by the developer to
 * define plugins, along with methods used by the Registry to manage plugin lifecycle.
 *
 * The developer API can be used to:
 *
 *    - register node modules as dependencies on the injector
 *    - include files in the plugin definition for managing large plugins
 *    - define dependencies using factory methods
 *    - alias dependencies
 *    - create adapters to determine which implementation of an interface to
 *      use at runtime
 *    - access dependencies for mutation without creating new dependencies
 *    - extend the plugin API
 *    - register callbacks for managing plugin lifecycle
 *
 */
class Plugin {

  /**
   * Constructor
   */
  constructor (name, metadata) {
    this.name = name
    this.metadata = metadata
  }

  /**
   * Require
   *
   * Adds dependencies to the injector by loading node modules. Accepts a string,
   * array or object. By passing an object you can alias the package name.
   *
   * Example
   *
   *    sunstone.plugin(<NAME>, <METADATA>)
   *      .require('express')
   *
   *      .require([
   *        'crypto',
   *        'ioredis'
   *      ])
   *
   *      .require({
   *        '_': 'lodash',
   *        'fs': 'fs-extra',
   *        'myLibrary': './myLibrary'
   *      })
   *
   * TODO
   * - path based requires are currently relative to this file.
   *   it might be better to make them relative to process.cwd()
   *   * require.resolve() can allow us to resolve the full path
   *     of a particular file, however in order to do so, it needs
   *     to be called from within the file itself.
   */
  require (modules) {
    if (typeof modules === 'string') {
      modules = { modules }
    }

    if (Array.isArray(modules)) {
      modules = _.zipObject(modules, modules)
    }

    Object.keys(modules).forEach((key) => {
      this.module(key, function () {
        return require(modules[key])
      })
    })

    return this
  }

  /**
   * Include
   *
   * The bootstrapping process searches through the plugins directory looking for
   * plugins to load. To make a new plugin you simply create a sub-directory with an
   * index.js file. Additional files in the directory are incorporated using the
   * include method. This allows developers to separate out one plugin into an
   * arbitrary number of files.
   *
   * Example:
   *
   *    // index.js
   *    'use strict'
   *
   *    module.exports = function (sunstone) {
   *      sunstone.plugin('MyResource', {
   *        version: '0.0.1',
   *        dependencies: {
   *          'Server': '0.0.1'
   *        }
   *      })
   *      .initializer(function (plugin) {
   *        .include('./other')
   *        .include('./yetanother')
   *      })
   *    }
   *
   *    // other.js
   *    'use strict'
   *
   *    module.exports = function (plugin) {
   *
   *      plugin
   *        .factory('MyModel', function (a, b) {
   *          // ...
   *        })
   *        .router('MyModelRouter', function (MyModel) {
   *          // ...
   *        })
   *
   *    }
   */
  include (filename) {
    // prepend file path from call stack onto the given, possibly relative, filename
    let caller = callsite()[1]
    let callerpath = caller.getFileName()
    let filepath = path.join(path.dirname(callerpath), filename)

    require(filepath)(this)

    return this
  }

  /**
   * Module
   *
   * This is used internally by `plugin.require()` to register modules with
   * the type "module". This is important to maintain a distinction between
   * components provided by plugins defined in the host (or extending applications)
   * and components that originate from node modules.
   */
  module (name, fn) {
    injector.register({
      name,
      type: 'module',
      plugin: this.name,
      fn
    })
  }

  /**
   * Factory
   *
   * The factory method registers a new dependency on the injector, validates it, and
   * determines which other dependencies it requires.
   *
   * The first argument is the name of the new dependency and the second argument is a
   * function that returns the value of the dependency. However, this dependency is not
   * invoked at the time the dependency is registered.
   *
   * Getting a dependency from the Injector invokes the function and stores the return
   * value.
   *
   * Example:
   *
   *    plugin
   *      .factory('one', function () {
   *        return 1
   *      })
   *      .factory('two', function () {
   *        return 2
   *      })
   *      .factory('oneplustwo', function (one, two) {
   *        return one + two
   *      })
   *
   */
  factory (name, fn) {
    injector.register({
      name,
      type: 'factory',
      plugin: this.name,
      fn
    })

    return this
  }

  /**
   * Adapter
   *
   * Create factories that determine which implementation
   * to use at injection time.
   *
   * Example:
   *
   *   sunstone.plugin(<NAME>, <METADATA>)
   *     .factory('RedisResource', function () {})
   *     .factory('MongoResource', function () {})
   *     .adapter('Resource', function (injector, settings) {
   *        // where settings.property is 'RedisResource' or 'MongoResource'
   *        return injector.get(settings.property)
   *     })
   *     .factory('User', function (Resource) {})
   *
   */
  adapter (name, fn) {
    injector.register({
      name,
      type: 'adapter',
      plugin: this.name,
      fn
    })

    return this
  }

  /**
   * Alias
   *
   * Alias creates a reference to another item on the injector
   * within its own injector object. When the alias is injected
   * through the use of the injector.get() method it calls the
   * aliased dependency and creates a reference to the instance
   * in the dependency.value field.
   *
   * Example:
   *
   *   sunstone.plugin(<NAME>, <METADATA)
   *   .factory('myDependency', function () {
   *     // ...
   *   })
   *
   *   .alias('myAlias', 'myDependency')
   *
   */
  alias (alias, name) {
    injector.register({
      name: alias,
      type: 'alias',
      plugin: this.name,
      fn: () => {
        return injector.get(name)
      }
    })

    return this
  }

  /**
   * Extension
   *
   * The idea of an extension is that you can access some component
   * to use it's API without registering anything on the injector.
   *
   * This is useful for things like modifying a model's schema or
   * registering event handlers on an event emitter.
   *
   * Example 1:
   *
   *   sunstone.plugin('Default API', <METADATA>)
   *     .factory('User', function (Resource) {
   *       class User extends Resource {
   *         static get schema () {
   *           return Object.assign({}, super.schema, {
   *             name: { type: 'string' },
   *             email: { type: 'string', format: 'email' }
   *           })
   *         }
   *       }
   *
   *       return User
   *     })
   *
   *   sunstone.plugin('My Project', <METADATA>)
   *     .extension('UserExtension', function (User) {
   *       User.extendSchema({
   *         domainSpecificAttribute: { type: 'whatever', ... }
   *       })
   *     })
   *
   * Example 2:
   *
   *   sunstone.plugin(<NAME>, <METADATA>)
   *     .factory('emitter', function () {
   *        return new EventEmitter()
   *     })
   *
   *   sunstone.plugin('My Project', <METADATA>)
   *     .extension('CustomEventHandlers', function (emitter) {
   *        emitter.on('ready', function (event) {
   *          // do something
   *        })
   *     })
   */
  extension (name, fn) {
    injector.register({
      name,
      type: 'extension',
      plugin: this.name,
      fn
    })

    return this
  }

  /**
   * Assembler
   *
   * This can be used to define new types of components. For example, the core
   * framework probably doesn't need any knowledge of Express routers, but if you
   * wanted to define a specialized factory registrar for routers, you could do it
   * like so:
   *
   *    sunstone.plugin('server', {
   *      version: '0.0.0'
   *    })
   *    .assembler('router', function (injector) {
   *      let plugin = this
   *      return function (name, factory) {
   *        injector.register({
   *          name,
   *          type: 'router',
   *          plugin: plugin.name,
   *          factory
   *        })
   *      })
   *    })
   *
   * This makes a new dependency registrar called 'router' that can be used thusly:
   *
   *    sunstone.plugin('other', {
   *      version: '0.0.0'
   *    })
   *    .router('SomeRouter', function (Router, SomeResource) {
   *      let router = new Router()
   *
   *      router.get('endpoint', function () {
   *        SomeResource
   *          .list(req.query)
   *          .then(function (results) {
   *            res.json(results)
   *          })
   *          .catch(error => next(error))
   *      })
   *
   *      return router
   *    })
   *
   * The dependency inject can then be queried by this new "type" value.
   *
   *    injector.find({ type: 'router' })
   *
   * TODO
   * - there should possibly be a way to create a starter method automatically for
   *   an assembler to save that boilerplate
   */
  assembler (name, fn) {
    this.constructor.prototype[name] = fn(injector, this)
    return this
  }

  /**
   * Lifecycle Management
   *
   * These methods are used to register lifecycle methods that will be called
   * by the plugin manager
   */

  /**
   * Initializer
   *
   * Example:
   *
   *    sunstone.
   */
  initializer (callback) {
    this[init] = callback
    return this
  }

  /**
   * Initialize
   */
  initialize () {
    let fn = this[init]

    if (fn) {
      fn(this)
    }

    return this
  }

  /**
   * Starter
   *
   * Example:
   *
   *    sunstone.plugin(<NAME>, <METADATA>)
   *      .initializer(function (plugin) {
   *        plugin.starter(function (injector, server) {
   *          injector
   *            .find({ plugin: this.name, type: 'router' })
   *            .values()
   *            .forEach(router => {
   *              router.mount(server)
   *            })
   *        })
   *      })
   */
  starter (fn) {
    injector.register({
      name: `${this.name}:starter`,
      type: 'callback',
      plugin: this.name,
      fn
    })

    return this
  }

  /**
   * Start
   */
  start () {
    injector.invoke(`${this.name}:starter`)
    return this
  }

  /**
   * Stopper
   */
  stopper (fn) {
    injector.register({
      name: `${this.name}:stopper`,
      type: 'callback',
      plugin: this.name,
      fn
    })

    return this
  }

  /**
   * Stop
   */
  stop () {
    injector.invoke(`${this.name}:stopper`)
    return this
  }

}

/**
 * Exports
 */
module.exports = Plugin
<|endoftext|>


<commit_before>'use strict';
var utils = require('./utils');
var msg = utils.msgFor('Injector');
function DI() {
    var register = {};
    return {
        add: addTo(register),
        get: getFrom(register),
        addAsync: addAsyncTo(register),
        getAsync: getAsyncFrom(register)
    };
}
module.exports = DI;
function addTo(reg, unused_argument) {
    return function (obj) {
        obj = obj || {};
        var name = obj.name;
        var impl = obj.impl;
        var deps = obj.deps;
        if (!name) throw msg('Module name should be specified');
        if (reg[name]) throw msg('Module <' + name + '> has been already registered');

        impl = typeof impl === 'function' ? impl : utils.constant(impl);
        reg[name] = {
            impl: impl,
            deps: deps
        };
        return this;
    };
}
function getFrom(reg) {
    return function resolve(deps, next) {
        var opts = deps.map(function (name) {
            var module = reg[name];
            if (!module) throw msg('Module <' + name + '> has not been registered');
            if (module.deps) return resolve(module.deps, module.impl);
            return module.impl();
        });
        if (next) return next.apply(null, opts);
        return function (cb) {
            return cb.apply(null, opts);
        };
    };
}
function addAsyncTo(reg) {
    return function (obj) {
        obj = obj || {};
        var name = obj.name;
        var impl = obj.impl;
        var deps = obj.deps;
        //if (!name) throw msg('Module name should be specified');
        //if (reg[name]) throw msg('Module <' + name + '> has been already registered');

        impl = typeof impl === 'function' ? impl : utils.constant(impl);
        var opts = (deps || []).map(function (name) {
            return reg[name].impl;
        });
        reg[name] = {
            impl: opts.length ? utils.deferrable(utils.asyncCompose(impl)).apply(null, opts) : utils.deferrable(impl)(),
            deps: deps
        };
        return this;
    };
}
function getAsyncFrom(reg) {
    return function resolveAsync(deps, next) {
        var opts = (deps || []).map(function (name) {
            var module = reg[name];
            if (!module) throw msg('Module <' + name + '> has not been registered');
            return module.impl;
        });
        opts.push(next);
        utils.asyncFor.apply(null, opts);
    };
}
<commit_msg>Remove unused argument<commit_after>'use strict';
var utils = require('./utils');
var msg = utils.msgFor('Injector');
function DI() {
    var register = {};
    return {
        add: addTo(register),
        get: getFrom(register),
        addAsync: addAsyncTo(register),
        getAsync: getAsyncFrom(register)
    };
}
module.exports = DI;
function addTo(reg) {
    return function (obj) {
        obj = obj || {};
        var name = obj.name;
        var impl = obj.impl;
        var deps = obj.deps;
        if (!name) throw msg('Module name should be specified');
        if (reg[name]) throw msg('Module <' + name + '> has been already registered');

        impl = typeof impl === 'function' ? impl : utils.constant(impl);
        reg[name] = {
            impl: impl,
            deps: deps
        };
        return this;
    };
}
function getFrom(reg) {
    return function resolve(deps, next) {
        var opts = deps.map(function (name) {
            var module = reg[name];
            if (!module) throw msg('Module <' + name + '> has not been registered');
            if (module.deps) return resolve(module.deps, module.impl);
            return module.impl();
        });
        if (next) return next.apply(null, opts);
        return function (cb) {
            return cb.apply(null, opts);
        };
    };
}
function addAsyncTo(reg) {
    return function (obj) {
        obj = obj || {};
        var name = obj.name;
        var impl = obj.impl;
        var deps = obj.deps;
        //if (!name) throw msg('Module name should be specified');
        //if (reg[name]) throw msg('Module <' + name + '> has been already registered');

        impl = typeof impl === 'function' ? impl : utils.constant(impl);
        var opts = (deps || []).map(function (name) {
            return reg[name].impl;
        });
        reg[name] = {
            impl: opts.length ? utils.deferrable(utils.asyncCompose(impl)).apply(null, opts) : utils.deferrable(impl)(),
            deps: deps
        };
        return this;
    };
}
function getAsyncFrom(reg) {
    return function resolveAsync(deps, next) {
        var opts = (deps || []).map(function (name) {
            var module = reg[name];
            if (!module) throw msg('Module <' + name + '> has not been registered');
            return module.impl;
        });
        opts.push(next);
        utils.asyncFor.apply(null, opts);
    };
}
<|endoftext|>


<commit_before>'use strict'

/**
 * Dependencies
 */
var _ = require('lodash')

/**
 * Symbols
 */
let injector = Symbol()

/**
 * Plugin
 *
 * TODO
 *  By itself this is not a plugin system. It's a way of
 *  grouping dependency definitions under a namespace with
 *  metadata (the manifest).
 *
 *  To make a plugin system, we need a way to dynamically
 *  load, reload, unload, instantiate, tear down, enable,
 *  disable, etc, while the application is running.
 *
 *  This capability is where the various
 *  `injector.register` functions may begin to
 *  differentiate themselves further.
 */
class Plugin {

  /**
   * Constructor
   */
  constructor (name, manifest, _injector) {
    this.name = name
    this.manifest = manifest
    this[injector] = _injector
  }

  /**
   * Require
   */
  require (modules) {
    if (Array.isArray(modules)) {
      modules = _.zipObject(modules, modules)
    }

    Object.keys(modules).forEach((key) => {
      this.factory(key, function () {
        return require(modules[key])
      })
    })

    return this
  }

  /**
   * Factory
   */
  factory (name, factory) {
    this[injector].register({
      name,
      type: 'factory',
      plugin: this.name,
      factory
    })

    return this
  }

  /**
   * Alias
   *
   * Create factories that determin which implementation
   * to use at injection time.
   *
   * Example:
   *
   *   sunstone.plugin(<NAME>, <MANIFEST>)
   *     .factory('a', function () {})
   *     .factory('b', function () {})
   *     .alias('c', function (injector, settings) {
   *        // where settings.property is 'a' or 'b'
   *        return injector.get(settings.property)
   *     })
   *     .factory('d', function (c) {})
   *
   *
   * TODO:
   *  - is "alias" the right name for this?
   *  - is there any way enforce that it's used this way?
   *  - is it even necessary or desirable to have this
   *    vs just using factory?
   */
  alias (name, factory) {
    this[injector].register({
      name,
      type: 'alias',
      plugin: this.name,
      factory
    })

    return this
  }

  /**
   * Extension
   */
  extension (name, mutator) {
    this[injector].register({
      name,
      type: 'extension',
      plugin: this.name,
      mutator
    })

    return this
  }

  /**
   * Router
   */
  router (name, factory) {
    this[injector].register({
      name,
      type: 'router',
      plugin: this.name,
      mutator
    })

    return this
  }

  /**
   * Connector
   */
  connector (name, factory) {
    this[injector].register({
      name,
      type: 'connector',
      plugin: this.name,
      mutator
    })

    return this
  }

  /**
   * Lifecycle Management
   */

  /**
   * Start
   */
  start () {}

  /**
   * Stop
   */
  stop () {}

}

/**
 * Exports
 */
module.exports = Plugin
<commit_msg>fix(plugin): correct property names<commit_after>'use strict'

/**
 * Dependencies
 */
var _ = require('lodash')

/**
 * Symbols
 */
let injector = Symbol()

/**
 * Plugin
 *
 * TODO
 *  By itself this is not a plugin system. It's a way of
 *  grouping dependency definitions under a namespace with
 *  metadata (the manifest).
 *
 *  To make a plugin system, we need a way to dynamically
 *  load, reload, unload, instantiate, tear down, enable,
 *  disable, etc, while the application is running.
 *
 *  This capability is where the various
 *  `injector.register` functions may begin to
 *  differentiate themselves further.
 */
class Plugin {

  /**
   * Constructor
   */
  constructor (name, manifest, _injector) {
    this.name = name
    this.manifest = manifest
    this[injector] = _injector
  }

  /**
   * Require
   */
  require (modules) {
    if (Array.isArray(modules)) {
      modules = _.zipObject(modules, modules)
    }

    Object.keys(modules).forEach((key) => {
      this.factory(key, function () {
        return require(modules[key])
      })
    })

    return this
  }

  /**
   * Factory
   */
  factory (name, factory) {
    this[injector].register({
      name,
      type: 'factory',
      plugin: this.name,
      factory
    })

    return this
  }

  /**
   * Alias
   *
   * Create factories that determin which implementation
   * to use at injection time.
   *
   * Example:
   *
   *   sunstone.plugin(<NAME>, <MANIFEST>)
   *     .factory('a', function () {})
   *     .factory('b', function () {})
   *     .alias('c', function (injector, settings) {
   *        // where settings.property is 'a' or 'b'
   *        return injector.get(settings.property)
   *     })
   *     .factory('d', function (c) {})
   *
   *
   * TODO:
   *  - is "alias" the right name for this?
   *  - is there any way enforce that it's used this way?
   *  - is it even necessary or desirable to have this
   *    vs just using factory?
   */
  alias (name, factory) {
    this[injector].register({
      name,
      type: 'alias',
      plugin: this.name,
      factory
    })

    return this
  }

  /**
   * Extension
   */
  extension (name, mutator) {
    this[injector].register({
      name,
      type: 'extension',
      plugin: this.name,
      mutator
    })

    return this
  }

  /**
   * Router
   */
  router (name, factory) {
    this[injector].register({
      name,
      type: 'router',
      plugin: this.name,
      factory
    })

    return this
  }

  /**
   * Connector
   */
  connector (name, factory) {
    this[injector].register({
      name,
      type: 'connector',
      plugin: this.name,
      factory
    })

    return this
  }

  /**
   * Lifecycle Management
   */

  /**
   * Start
   */
  start () {}

  /**
   * Stop
   */
  stop () {}

}

/**
 * Exports
 */
module.exports = Plugin
<|endoftext|>


<commit_before>import { module } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { set } from '@ember/object';
import { click, render, settled, waitFor } from '@ember/test-helpers';
import { test, testRequiringTransitions } from '../../helpers/bootstrap';
import hbs from 'htmlbars-inline-precompile';
import setupNoDeprecations from '../../helpers/setup-no-deprecations';
import a11yAudit from 'ember-a11y-testing/test-support/audit';
import sinon from 'sinon';

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

  hooks.beforeEach(function () {
    this.actions = {};
    this.send = (actionName, ...args) => this.actions[actionName].apply(this, args);
  });

  test('alert has correct CSS classes', async function (assert) {
    await render(hbs`<BsAlert @type="success" @dismissible={{true}}>Test</BsAlert>`);

    assert.dom('.alert').exists('alert has alert class');
    assert.dom('.alert').hasClass('alert-success', 'alert has type class');
    assert.dom('.alert').hasClass('alert-dismissible', 'alert has dismissible class');
  });

  test('dismissible alert can be hidden by clicking close button with fade=false', async function (assert) {
    await render(hbs`<BsAlert @type="success" @fade={{false}}>Test</BsAlert>`);

    assert.dom('button.close').exists({ count: 1 }, 'alert has close button');
    await click('button.close');

    assert.dom('.alert').doesNotExist('alert has no alert class');
    assert.dom('*').hasText('', 'alert has no content');
  });

  testRequiringTransitions('dismissible alert can be hidden by clicking close button with fade=true', async function (
    assert
  ) {
    await render(hbs`<BsAlert @type="success" @fade={{true}}>Test</BsAlert>`);

    assert.dom('button.close').exists({ count: 1 }, 'alert has close button');
    let promise = click('button.close');
    await waitFor('.alert:not(.in)');

    assert.dom('.alert').exists('alert has alert class');
    assert.dom('.alert').hasNoClass('in', 'alert has no in class');

    await promise;
    assert.dom('.alert').doesNotExist('alert has no alert class');
    assert.dom('*').hasText('', 'alert has no content');
  });

  test('alert can be hidden by setting visible property', async function (assert) {
    this.set('visible', true);
    await render(hbs`<BsAlert @type="success" @fade={{false}} @visible={{visible}}>Test</BsAlert>`);

    this.set('visible', false);

    assert.dom('.alert').doesNotExist('alert has no alert class');
    assert.dom('*').hasText('', 'alert has no content');
  });

  test('onDismiss is called when clicking the close button', async function (assert) {
    let action = sinon.spy();
    this.actions.dismiss = action;
    await render(hbs`<BsAlert @type="success" @fade={{false}} @onDismiss={{action "dismiss"}}>Test</BsAlert>`);
    await click('button.close');
    assert.ok(action.calledOnce, 'onDismiss has been called.');
  });

  test('alert is not hidden after clicking the close button when onDismiss action return false', async function (assert) {
    let action = sinon.stub();
    action.returns(false);
    this.actions.dismiss = action;
    await render(hbs`<BsAlert @type="success" @fade={{false}} @onDismiss={{action "dismiss"}}>Test</BsAlert>`);
    await click('button.close');
    assert.ok(action.calledOnce, 'onDismiss has been called.');

    assert.dom('.alert').exists('alert is still visible');
  });

  test('onDismissed is called after modal is closed', async function (assert) {
    let action = sinon.spy();
    this.actions.dismissed = action;
    await render(hbs`<BsAlert @type="success" @fade={{false}} @onDismissed={{action "dismissed"}}>Test</BsAlert>`);
    await click('button.close');
    assert.ok(action.calledOnce, 'onDismissed has been called.');
  });

  test('When fade is true, onDismissed is called once when visible changes from true to false', async function (assert) {
    let action = sinon.spy();
    this.dismissed = action;
    this.isAlertShown = true;

    await render(
      hbs`<BsAlert @fade={{true}} @onDismissed={{this.dismissed}} @visible={{this.isAlertShown}}>Test</BsAlert>`
    );

    set(this, 'isAlertShown', false);
    await settled();

    assert.ok(action.calledOnce, 'onDismissed is called once.');
  });

  test('When fade is false, onDismissed is called once when visible changes from true to false', async function (assert) {
    let action = sinon.spy();
    this.dismissed = action;
    this.isAlertShown = true;

    await render(
      hbs`<BsAlert @fade={{false}} @onDismissed={{this.dismissed}} @visible={{this.isAlertShown}}>Test</BsAlert>`
    );

    set(this, 'isAlertShown', false);
    await settled();

    assert.ok(action.calledOnce, 'onDismissed is called once.');
  });

  test('alert is initially hidden when visible=false', async function (assert) {
    await render(hbs`<BsAlert @type="success" @fade={{false}} @visible={{false}}>Test</BsAlert>`);

    assert.dom('.alert').doesNotExist('alert has no alert class');
    assert.dom('*').hasText('', 'alert has no content');
  });

  test('alert can be made visible when setting visible=true', async function (assert) {
    this.set('visible', false);
    await render(hbs`<BsAlert @type="success" @visible={{visible}} @fade={{false}}>Test</BsAlert>`);
    this.set('visible', true);

    assert.dom('.alert').exists('alert has alert class');
    assert.dom('.alert').hasClass('alert-success', 'alert has type class');
  });

  test('dismissing alert does not change public visible property (DDAU)', async function (assert) {
    this.set('visible', true);
    await render(hbs`<BsAlert @type="success" @visible={{visible}} @fade={{false}}>Test</BsAlert>`);

    await click('button.close');

    assert.equal(this.visible, true, 'Does not modify visible property');
  });

  test('it passes accessibility checks', async function (assert) {
    await render(hbs`<BsAlert @type="success">Test</BsAlert>`);

    await a11yAudit();
    assert.ok(true, 'A11y audit passed');
  });
});
<commit_msg>Removed the use of set()<commit_after>import { module } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { click, render, settled, waitFor } from '@ember/test-helpers';
import { test, testRequiringTransitions } from '../../helpers/bootstrap';
import hbs from 'htmlbars-inline-precompile';
import setupNoDeprecations from '../../helpers/setup-no-deprecations';
import a11yAudit from 'ember-a11y-testing/test-support/audit';
import sinon from 'sinon';

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

  hooks.beforeEach(function () {
    this.actions = {};
    this.send = (actionName, ...args) => this.actions[actionName].apply(this, args);
  });

  test('alert has correct CSS classes', async function (assert) {
    await render(hbs`<BsAlert @type="success" @dismissible={{true}}>Test</BsAlert>`);

    assert.dom('.alert').exists('alert has alert class');
    assert.dom('.alert').hasClass('alert-success', 'alert has type class');
    assert.dom('.alert').hasClass('alert-dismissible', 'alert has dismissible class');
  });

  test('dismissible alert can be hidden by clicking close button with fade=false', async function (assert) {
    await render(hbs`<BsAlert @type="success" @fade={{false}}>Test</BsAlert>`);

    assert.dom('button.close').exists({ count: 1 }, 'alert has close button');
    await click('button.close');

    assert.dom('.alert').doesNotExist('alert has no alert class');
    assert.dom('*').hasText('', 'alert has no content');
  });

  testRequiringTransitions('dismissible alert can be hidden by clicking close button with fade=true', async function (
    assert
  ) {
    await render(hbs`<BsAlert @type="success" @fade={{true}}>Test</BsAlert>`);

    assert.dom('button.close').exists({ count: 1 }, 'alert has close button');
    let promise = click('button.close');
    await waitFor('.alert:not(.in)');

    assert.dom('.alert').exists('alert has alert class');
    assert.dom('.alert').hasNoClass('in', 'alert has no in class');

    await promise;
    assert.dom('.alert').doesNotExist('alert has no alert class');
    assert.dom('*').hasText('', 'alert has no content');
  });

  test('alert can be hidden by setting visible property', async function (assert) {
    this.set('visible', true);
    await render(hbs`<BsAlert @type="success" @fade={{false}} @visible={{visible}}>Test</BsAlert>`);

    this.set('visible', false);

    assert.dom('.alert').doesNotExist('alert has no alert class');
    assert.dom('*').hasText('', 'alert has no content');
  });

  test('onDismiss is called when clicking the close button', async function (assert) {
    let action = sinon.spy();
    this.actions.dismiss = action;
    await render(hbs`<BsAlert @type="success" @fade={{false}} @onDismiss={{action "dismiss"}}>Test</BsAlert>`);
    await click('button.close');
    assert.ok(action.calledOnce, 'onDismiss has been called.');
  });

  test('alert is not hidden after clicking the close button when onDismiss action return false', async function (assert) {
    let action = sinon.stub();
    action.returns(false);
    this.actions.dismiss = action;
    await render(hbs`<BsAlert @type="success" @fade={{false}} @onDismiss={{action "dismiss"}}>Test</BsAlert>`);
    await click('button.close');
    assert.ok(action.calledOnce, 'onDismiss has been called.');

    assert.dom('.alert').exists('alert is still visible');
  });

  test('onDismissed is called after modal is closed', async function (assert) {
    let action = sinon.spy();
    this.actions.dismissed = action;
    await render(hbs`<BsAlert @type="success" @fade={{false}} @onDismissed={{action "dismissed"}}>Test</BsAlert>`);
    await click('button.close');
    assert.ok(action.calledOnce, 'onDismissed has been called.');
  });

  test('When fade is true, onDismissed is called once when visible changes from true to false', async function (assert) {
    let action = sinon.spy();
    this.dismissed = action;
    this.isAlertShown = true;

    await render(
      hbs`<BsAlert @fade={{true}} @onDismissed={{this.dismissed}} @visible={{this.isAlertShown}}>Test</BsAlert>`
    );

    this.set('isAlertShown', false);
    await settled();

    assert.ok(action.calledOnce, 'onDismissed is called once.');
  });

  test('When fade is false, onDismissed is called once when visible changes from true to false', async function (assert) {
    let action = sinon.spy();
    this.dismissed = action;
    this.isAlertShown = true;

    await render(
      hbs`<BsAlert @fade={{false}} @onDismissed={{this.dismissed}} @visible={{this.isAlertShown}}>Test</BsAlert>`
    );

    this.set('isAlertShown', false);
    await settled();

    assert.ok(action.calledOnce, 'onDismissed is called once.');
  });

  test('alert is initially hidden when visible=false', async function (assert) {
    await render(hbs`<BsAlert @type="success" @fade={{false}} @visible={{false}}>Test</BsAlert>`);

    assert.dom('.alert').doesNotExist('alert has no alert class');
    assert.dom('*').hasText('', 'alert has no content');
  });

  test('alert can be made visible when setting visible=true', async function (assert) {
    this.set('visible', false);
    await render(hbs`<BsAlert @type="success" @visible={{visible}} @fade={{false}}>Test</BsAlert>`);
    this.set('visible', true);

    assert.dom('.alert').exists('alert has alert class');
    assert.dom('.alert').hasClass('alert-success', 'alert has type class');
  });

  test('dismissing alert does not change public visible property (DDAU)', async function (assert) {
    this.set('visible', true);
    await render(hbs`<BsAlert @type="success" @visible={{visible}} @fade={{false}}>Test</BsAlert>`);

    await click('button.close');

    assert.equal(this.visible, true, 'Does not modify visible property');
  });

  test('it passes accessibility checks', async function (assert) {
    await render(hbs`<BsAlert @type="success">Test</BsAlert>`);

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


<commit_before>define(
  ['model-application', 'authentication'],
  function (app, Auth) {
    'use strict';

    function start() {
      // AJAX Default Options
      $.ajaxPrefilter(function (options, originalOptions, jqXHR) {
        jqXHR.setRequestHeader('X-Blink-Config',
          JSON.stringify(window.BMP.BIC.siteVars));
      });

      window.BMP.Authentication = new Auth();

      require(['router', 'auth']);
    }

    // Delay the app for Cordova
    function init() {
      if (window.BMP.BlinkGap.isHere()) {
        window.BMP.BlinkGap.whenReady().then(start, start);
      } else {
        start();
      }
    }

    // Save traditional sync method as ajaxSync
    Backbone.ajaxSync = Backbone.sync;

    // New sync method
    Backbone.dataSync = function (method, model, options) {
      var data, promise;
      data = model.data || model.collection.data;

      switch (method) {
      case 'read':
        promise = model.id !== undefined ? data.read(model) : data.readAll();
        break;
      case 'create':
        promise = data.create(model);
        break;
      case 'update':
        promise = data.update(model);
        break;
      case 'patch':
        promise = data.update(model);
        break;
      case 'delete':
        promise = data.delete(model);
        break;
      default:
        promise = Promise.reject(new Error('unknown method'));
      }

      promise.then(function (response) {
        if (options.success) {
          options.success(response);
        }
      }, function (response) {
        if (options.error) {
          options.error(response);
        }
      });

      model.trigger('request', model, promise, options);

      return promise;
    };

    // Fallback to traditional sync when not specified
    Backbone.getSyncMethod = function (model) {
      if (model.data || model.collection && model.collection.data) {
        return Backbone.dataSync;
      }
      return Backbone.ajaxSync;
    };

    // Hook Backbone.sync up to the data layer
    Backbone.sync = function (method, model, options) {
      return Backbone.getSyncMethod(model).apply(this, [method, model, options]);
    };

    init();

    return app; // export BMP.BIC
  }
);
<commit_msg>BIC-147 # fix Backbone Collections in lieu of persistence<commit_after>define(
  ['model-application', 'authentication'],
  function (app, Auth) {
    'use strict';

    function start() {
      // AJAX Default Options
      $.ajaxPrefilter(function (options, originalOptions, jqXHR) {
        jqXHR.setRequestHeader('X-Blink-Config',
          JSON.stringify(window.BMP.BIC.siteVars));
      });

      window.BMP.Authentication = new Auth();

      require(['router', 'auth']);
    }

    // Save traditional sync method as ajaxSync
    Backbone.ajaxSync = Backbone.sync;

    // New sync method
    Backbone.dataSync = function (method, model, options) {
      var data, promise;
      data = model.data || model.collection.data;

      switch (method) {
      case 'read':
        promise = model.id !== undefined ? data.read(model) : data.readAll();
        break;
      case 'create':
        promise = data.create(model);
        break;
      case 'update':
        promise = data.update(model);
        break;
      case 'patch':
        promise = data.update(model);
        break;
      case 'delete':
        promise = data.delete(model);
        break;
      default:
        promise = Promise.reject(new Error('unknown method'));
      }

      promise.then(function (response) {
        if (options.success) {
          options.success(response);
        }
      }, function (response) {
        if (options.error) {
          options.error(response);
        }
      });

      model.trigger('request', model, promise, options);

      return promise;
    };

    // Fallback to traditional sync when not specified
    Backbone.getSyncMethod = function (model) {
      if (model.data || model.collection && model.collection.data) {
        return Backbone.dataSync;
      }
      return Backbone.ajaxSync;
    };

    if (app.hasStorage()) {
      // Hook Backbone.sync up to the data layer
      Backbone.sync = function (method, model, options) {
        return Backbone.getSyncMethod(model).apply(this, [method, model, options]);
      };
    } else {
      Backbone.sync = function (method, model, options) { options.success(); };
    }

    if (window.BMP.BlinkGap.isHere()) {
      // Delay the app for Cordova
      window.BMP.BlinkGap.whenReady().then(start, start);
    } else {
      start();
    }

    return app; // export BMP.BIC
  }
);
<|endoftext|>


<commit_before>import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';

moduleForComponent( 'sl-panel', 'Integration | Component | sl panel', {
    integration: true
});

test( 'Default classes are present', function( assert ) {
    this.render( hbs`
        {{#sl-panel}}
        {{/sl-panel}}
    ` );

    assert.ok(
        this.$( '>:first-child' ).hasClass( 'panel' ),
        'Default rendered component has class "panel"'
    );

    assert.ok(
        this.$( '>:first-child' ).hasClass( 'panel-default' ),
        'Default rendered component has class "panel-default"'
    );

    assert.ok(
        this.$( '>:first-child' ).hasClass( 'sl-panel' ),
        'Default rendered component has class "sl-panel"'
    );

    assert.ok(
        this.$( '>:first-child' ).find( 'panel-body' ),
        'Default rendered component has class "panel-body"'
    );

    assert.ok(
        this.$( '>:first-child' ).find( 'sl-maskable-content' ),
        'Default rendered component has class "sl-maskable-content"'
    );

    assert.ok(
        this.$( '>:first-child' ).find( 'sl-mask' ),
        'Default rendered component has class "sl-mask"'
    );
});

test( 'Valid structure rendered for sl-panel', function( assert ) {
    this.render( hbs`
        {{#sl-panel}}
        {{/sl-panel}}
    ` );

    const component = this.$( '>:first-child' );

    assert.strictEqual(
      component.children()[0].className,
      'panel-body',
      'Parent node rendered is "panel-body"'
    );

    assert.strictEqual(
      component.children().children()[0].className,
      'sl-maskable-content',
      'First child of Parent Node rendered is "sl-maskable-content"'
    );

    assert.strictEqual(
      component.children().children()[1].className,
      'sl-mask',
      'Second child of Parent Node rendered is "sl-mask"'
    );
});

test( 'Valid heading value renders panel-heading', function( assert ) {
    this.render( hbs`
      {{#sl-panel}}
      {{/sl-panel}}
    ` );

    assert.strictEqual(
        this.$( '>:first-child' ).find( '.panel-heading' ).length,
        0,
        'Default rendered component has no .panel-heading'
    );

    this.render( hbs`
      {{#sl-panel heading="Test"}}
      {{/sl-panel}}
    ` );

    assert.strictEqual(
        this.$( '>:first-child' ).find( '.panel-heading' ).length,
        1,
        'Rendered component has panel-heading text'
    );

    assert.strictEqual(
        this.$( '>:first-child' ).find( '.panel-heading' ).text(),
        'Test',
        'Text of rendered heading is equal to value passed to it'
    );
});

test( 'Loading state applies class name', function( assert ) {
    this.render( hbs`
      {{#sl-panel}}
      {{/sl-panel}}
    ` );

    assert.strictEqual(
        this.$( '>:first-child' ).hasClass( 'sl-loading' ),
        false,
        'Default rendered component does not have class "sl-loading"'
    );

    this.render( hbs`
      {{#sl-panel loading=true}}
      {{/sl-panel}}
    ` );

    assert.ok(
        this.$( '>:first-child' ).hasClass( 'sl-loading' ),
        'Rendered component has class "sl-loading"'
    );
});

test( 'Content is yielded', function( assert ) {
    this.render( hbs`
        {{#sl-panel}}

        {{/sl-panel}}
    ` );

    assert.strictEqual(
        this.$( '>:first-child' ).find( '.test' ).length,
        1,
        'Content yields successfully'
    );
});
<commit_msg>Use inline component form where appropriate.<commit_after>import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';

moduleForComponent( 'sl-panel', 'Integration | Component | sl panel', {
    integration: true
});

test( 'Default classes are present', function( assert ) {
    this.render( hbs`
        {{sl-panel}}
    ` );

    assert.ok(
        this.$( '>:first-child' ).hasClass( 'panel' ),
        'Default rendered component has class "panel"'
    );

    assert.ok(
        this.$( '>:first-child' ).hasClass( 'panel-default' ),
        'Default rendered component has class "panel-default"'
    );

    assert.ok(
        this.$( '>:first-child' ).hasClass( 'sl-panel' ),
        'Default rendered component has class "sl-panel"'
    );

    assert.ok(
        this.$( '>:first-child' ).find( 'panel-body' ),
        'Default rendered component has class "panel-body"'
    );

    assert.ok(
        this.$( '>:first-child' ).find( 'sl-maskable-content' ),
        'Default rendered component has class "sl-maskable-content"'
    );

    assert.ok(
        this.$( '>:first-child' ).find( 'sl-mask' ),
        'Default rendered component has class "sl-mask"'
    );
});

test( 'Valid structure rendered for sl-panel', function( assert ) {
    this.render( hbs`
        {{sl-panel}}
    ` );

    const component = this.$( '>:first-child' );

    assert.strictEqual(
      component.children()[0].className,
      'panel-body',
      'Parent node rendered is "panel-body"'
    );

    assert.strictEqual(
      component.children().children()[0].className,
      'sl-maskable-content',
      'First child of Parent Node rendered is "sl-maskable-content"'
    );

    assert.strictEqual(
      component.children().children()[1].className,
      'sl-mask',
      'Second child of Parent Node rendered is "sl-mask"'
    );
});

test( 'Valid heading value renders panel-heading', function( assert ) {
    this.render( hbs`
        {{sl-panel}}
    ` );

    assert.strictEqual(
        this.$( '>:first-child' ).find( '.panel-heading' ).length,
        0,
        'Default rendered component has no .panel-heading'
    );

    this.render( hbs`
        {{sl-panel heading="Test"}}
    ` );

    assert.strictEqual(
        this.$( '>:first-child' ).find( '.panel-heading' ).length,
        1,
        'Rendered component has panel-heading text'
    );

    assert.strictEqual(
        this.$( '>:first-child' ).find( '.panel-heading' ).text(),
        'Test',
        'Text of rendered heading is equal to value passed to it'
    );
});

test( 'Loading state applies class name', function( assert ) {
    this.render( hbs`
        {{sl-panel}}
    ` );

    assert.strictEqual(
        this.$( '>:first-child' ).hasClass( 'sl-loading' ),
        false,
        'Default rendered component does not have class "sl-loading"'
    );

    this.render( hbs`
        {{sl-panel loading=true}}
    ` );

    assert.ok(
        this.$( '>:first-child' ).hasClass( 'sl-loading' ),
        'Rendered component has class "sl-loading"'
    );
});

test( 'Content is yielded', function( assert ) {
    this.render( hbs`
        {{#sl-panel}}

        {{/sl-panel}}
    ` );

    assert.strictEqual(
        this.$( '>:first-child' ).find( '.test' ).length,
        1,
        'Content yields successfully'
    );
});
<|endoftext|>


<commit_before><commit_msg>Update dependencies<commit_after><|endoftext|>


<commit_before>import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';

const defaultTemplate = hbs`
    {{sl-radio}}
`;

moduleForComponent( 'sl-radio', 'Integration | Component | sl radio', {
    integration: true
});

test( 'Default rendered state', function( assert ) {
    this.render( defaultTemplate );

    assert.ok(
        this.$( '>:first-child' ).hasClass( 'sl-radio' ),
        'Has class "sl-radio"'
    );

    assert.ok(
        this.$( '>:first-child' ).hasClass( 'radio' ),
        'Has class "radio"'
    );

    assert.strictEqual(
        this.$( '>:first-child' ).find( 'input[type="radio"]' ).length,
        1,
        'input type is set to "radio"'
    );
});

test( 'Disabled state applies disabled class, and attribute to input', function( assert ) {
    this.render( hbs`
        {{sl-radio disabled=true}}
    ` );

    assert.ok(
        this.$( '>:first-child' ).find( 'input' ).prop( 'disabled' ),
        'has attribute "disabled"'
    );

    assert.ok(
        this.$( '>:first-child' ).hasClass( 'disabled' ),
        'has class "disabled"'
    );
});

test( 'Inline property sets relevant class', function( assert ) {
    this.render( hbs`
        {{sl-radio inline=true}}
    ` );

    assert.ok(
        this.$( '>:first-child' ).hasClass( 'radio-inline' ),
        'has class "radio-inline"'
    );
});

test( 'name applies property to input', function( assert ) {
    this.render( defaultTemplate );

    assert.strictEqual(
        this.$( '>:first-child' ).find( 'input' ).prop( 'name' ),
        '',
        'Rendered input has empty name'
    );

    this.render( hbs`
        {{sl-radio name="testname"}}
    ` );

    assert.strictEqual(
        this.$( '>:first-child' ).find( 'input' ).prop( 'name' ),
        'testname',
        'Rendered input has name set'
    );
});

test( '"value" property is supported', function( assert ) {
    this.render( defaultTemplate );

    assert.strictEqual(
        this.$( '>:first-child' ).find( 'input' ).val(),
        'on',
        'input value set to default value of "on"'
    );

    this.set( 'valueTest', 'testValue' );

    this.render( hbs`
        {{sl-radio value=valueTest}}
    ` );

    assert.strictEqual(
        this.$( '>:first-child' ).find( 'input' ).val(),
        'testValue',
        '"value" is set'
    );
});

test( '"label" property is supported', function( assert ) {
    this.render( defaultTemplate );

    assert.strictEqual(
        this.$( '>:first-child' ).find( 'label' ).text().trim(),
        '',
        '"label" text is not set'
    );

    this.set( 'labelTest', 'testLabel' );

    this.render( hbs`
        {{sl-radio label=labelTest}}
    ` );

    assert.strictEqual(
        this.$( '>:first-child' ).find( 'label' ).text().trim(),
        'testLabel',
        '"label" text gets set'
    );
});
<commit_msg>Removed two unneeded asserts<commit_after>import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';

const defaultTemplate = hbs`
    {{sl-radio}}
`;

moduleForComponent( 'sl-radio', 'Integration | Component | sl radio', {
    integration: true
});

test( 'Default rendered state', function( assert ) {
    this.render( defaultTemplate );

    assert.ok(
        this.$( '>:first-child' ).hasClass( 'sl-radio' ),
        'Has class "sl-radio"'
    );

    assert.ok(
        this.$( '>:first-child' ).hasClass( 'radio' ),
        'Has class "radio"'
    );

    assert.strictEqual(
        this.$( '>:first-child' ).find( 'input[type="radio"]' ).length,
        1,
        'input type is set to "radio"'
    );
});

test( 'Disabled state applies disabled class, and attribute to input', function( assert ) {
    this.render( hbs`
        {{sl-radio disabled=true}}
    ` );

    assert.ok(
        this.$( '>:first-child' ).find( 'input' ).prop( 'disabled' ),
        'has attribute "disabled"'
    );

    assert.ok(
        this.$( '>:first-child' ).hasClass( 'disabled' ),
        'has class "disabled"'
    );
});

test( 'Inline property sets relevant class', function( assert ) {
    this.render( hbs`
        {{sl-radio inline=true}}
    ` );

    assert.ok(
        this.$( '>:first-child' ).hasClass( 'radio-inline' ),
        'has class "radio-inline"'
    );
});

test( 'name applies property to input', function( assert ) {
    this.render( defaultTemplate );

    assert.strictEqual(
        this.$( '>:first-child' ).find( 'input' ).prop( 'name' ),
        '',
        'Rendered input has empty name'
    );

    this.render( hbs`
        {{sl-radio name="testname"}}
    ` );

    assert.strictEqual(
        this.$( '>:first-child' ).find( 'input' ).prop( 'name' ),
        'testname',
        'Rendered input has name set'
    );
});

test( '"value" property is supported', function( assert ) {
    this.render( hbs`
        {{sl-radio value="testValue"}}
    ` );

    assert.strictEqual(
        this.$( '>:first-child' ).find( 'input' ).val(),
        'testValue',
        '"value" is set'
    );
});

test( '"label" property is supported', function( assert ) {
    this.render( hbs`
        {{sl-radio label="testLabel"}}
    ` );

    assert.strictEqual(
        this.$( '>:first-child' ).find( 'label' ).text().trim(),
        'testLabel',
        '"label" text gets set'
    );
});
<|endoftext|>