<commit_before>import { i10n } from 'toolkit/extension/utils/toolkit';
import { getEntityManager } from 'toolkit/extension/utils/ynab';
import { Feature } from 'toolkit/extension/features/feature';
import { currentRouteIsBudgetPage, getSidebarViewModel } from 'toolkit/extension/utils/ynab';
import { formatCurrency } from 'toolkit/extension/utils/currency';

export class DaysOfBuffering extends Feature {
  _lookbackMonths = parseInt(ynabToolKit.options.DaysOfBufferingHistoryLookup);
  _lookbackDays = this._lookbackMonths * 30;

  injectCSS() { return require('./index.css'); }

  shouldInvoke() {
    return currentRouteIsBudgetPage() && !document.querySelector('toolkit-days-of-buffering');
  }

  invoke() {
    const elligibleTransactions = getEntityManager().getAllTransactions().filter(this._elligibleTransactionFilter);
    const onBudgetBalance = getSidebarViewModel().getOnBudgetAccountsBalance();
    const calcuation = this._calculateDaysOfBuffering(onBudgetBalance, elligibleTransactions);
    this._updateDisplay(calcuation);
  }

  onRouteChanged() {
    if (this.shouldInvoke()) {
      this.invoke();
    }
  }

  _updateDisplay(calcuation) {
    const { averageDailyOutflow, daysOfBuffering, totalDays, totalOutflow } = calcuation;
    const daysOfBufferingContainer = document.querySelector('.toolkit-days-of-buffering');
    let $displayElement = $(daysOfBufferingContainer);
    if (!daysOfBufferingContainer) {
      $displayElement = $('<div>', { class: 'budget-header-item budget-header-days toolkit-days-of-buffering' })
        .append($('<div>', {
          class: 'budget-header-days-age',
          title: i10n('budget.dob.tooltip', 'Don\'t like AoM? Try this out instead!')
        }))
        .append($('<div>', {
          class: 'budget-header-days-label',
          text: i10n('budget.dob.title', 'Days of Buffering'),
          title: i10n('budget.dob.tooltip', 'Don\'t like AoM? Try this out instead!')
        }));

      $('.budget-header-flexbox').append($displayElement);
    }

    if (calcuation.notEnoughDates) {
      $('.budget-header-days-age', $displayElement).text('???');
      $('.budget-header-days-age', $displayElement).attr('title', i10n('budget.dob.noHistory', 'Your budget history is less than 15 days. Go on with YNAB a while.'));
    } else {
      const dayText = daysOfBuffering === 1.0 ? i10n('budget.ageOfMoneyDays.one', 'day') : i10n('budget.ageOfMoneyDays.other', 'days');
      $('.budget-header-days-age', $displayElement).text(`${daysOfBuffering} ${dayText}`);
      $('.budget-header-days-age', $displayElement).attr('title', `${i10n('budget.dob.outflow', 'Total outflow')}: ${formatCurrency(totalOutflow)}
${i10n('budget.dob.days', 'Total days of budgeting')}: ${totalDays}
${i10n('budget.dob.avgOutflow', 'Average daily outflow')}: ~${formatCurrency(averageDailyOutflow)}`);
    }
  }

  _calculateDaysOfBuffering = (balance, transactions) => {
    const { dates, totalOutflow, uniqueDates } = transactions.reduce((reduced, current) => {
      const { amount, date } = current.getProperties('amount', 'date');
      reduced.dates.push(date.toUTCMoment());
      reduced.uniqueDates.set(date.format());
      reduced.totalOutflow += amount;
      return reduced;
    }, { dates: [], totalOutflow: 0, uniqueDates: new Map() });

    const minDate = moment.min(dates);
    const maxDate = moment.max(dates);
    const availableDates = maxDate.diff(minDate, 'days');

    let averageDailyOutflow;
    if (this._lookbackDays !== 0) {
      averageDailyOutflow = Math.abs(totalOutflow / this._lookbackDays);
    } else {
      averageDailyOutflow = Math.abs(totalOutflow / availableDates);
    }

    let daysOfBuffering = balance / averageDailyOutflow;
    if (daysOfBuffering < 10) {
      daysOfBuffering = daysOfBuffering.toFixed(1);
    } else {
      daysOfBuffering = Math.floor(daysOfBuffering);
    }

    const notEnoughDates = uniqueDates.size < 15;
    if (notEnoughDates) {
      daysOfBuffering = null;
    }

    return {
      averageDailyOutflow,
      daysOfBuffering,
      notEnoughDates,
      totalDays: dates.size,
      totalOutflow
    };
  }

  _elligibleTransactionFilter = (transaction) => {
    const today = new ynab.utilities.DateWithoutTime();

    let isElligibleDate = false;
    if (this._lookbackDays === 0) {
      isElligibleDate = true;
    } else {
      isElligibleDate = transaction.get('date').daysApart(today) < this._lookbackDays;
    }

    return (
      isElligibleDate &&
      !transaction.get('isTombstone') &&
      !transaction.get('payee.isInternal') &&
      !transaction.isTransferTransaction() &&
      transaction.get('account.onBudget') &&
      transaction.get('amount') < 0
    );
  }
}
<commit_msg>Fix typos in the days of buffering feature (#1332)<commit_after>import { i10n } from 'toolkit/extension/utils/toolkit';
import { getEntityManager } from 'toolkit/extension/utils/ynab';
import { Feature } from 'toolkit/extension/features/feature';
import { currentRouteIsBudgetPage, getSidebarViewModel } from 'toolkit/extension/utils/ynab';
import { formatCurrency } from 'toolkit/extension/utils/currency';

export class DaysOfBuffering extends Feature {
  _lookbackMonths = parseInt(ynabToolKit.options.DaysOfBufferingHistoryLookup);
  _lookbackDays = this._lookbackMonths * 30;

  injectCSS() { return require('./index.css'); }

  shouldInvoke() {
    return currentRouteIsBudgetPage() && !document.querySelector('toolkit-days-of-buffering');
  }

  invoke() {
    const eligibleTransactions = getEntityManager().getAllTransactions().filter(this._eligibleTransactionFilter);
    const onBudgetBalance = getSidebarViewModel().getOnBudgetAccountsBalance();
    const calculation = this._calculateDaysOfBuffering(onBudgetBalance, eligibleTransactions);
    this._updateDisplay(calculation);
  }

  onRouteChanged() {
    if (this.shouldInvoke()) {
      this.invoke();
    }
  }

  _updateDisplay(calculation) {
    const { averageDailyOutflow, daysOfBuffering, totalDays, totalOutflow } = calculation;
    const daysOfBufferingContainer = document.querySelector('.toolkit-days-of-buffering');
    let $displayElement = $(daysOfBufferingContainer);
    if (!daysOfBufferingContainer) {
      $displayElement = $('<div>', { class: 'budget-header-item budget-header-days toolkit-days-of-buffering' })
        .append($('<div>', {
          class: 'budget-header-days-age',
          title: i10n('budget.dob.tooltip', 'Don\'t like AoM? Try this out instead!')
        }))
        .append($('<div>', {
          class: 'budget-header-days-label',
          text: i10n('budget.dob.title', 'Days of Buffering'),
          title: i10n('budget.dob.tooltip', 'Don\'t like AoM? Try this out instead!')
        }));

      $('.budget-header-flexbox').append($displayElement);
    }

    if (calculation.notEnoughDates) {
      $('.budget-header-days-age', $displayElement).text('???');
      $('.budget-header-days-age', $displayElement).attr('title', i10n('budget.dob.noHistory', 'Your budget history is less than 15 days. Go on with YNAB a while.'));
    } else {
      const dayText = daysOfBuffering === 1.0 ? i10n('budget.ageOfMoneyDays.one', 'day') : i10n('budget.ageOfMoneyDays.other', 'days');
      $('.budget-header-days-age', $displayElement).text(`${daysOfBuffering} ${dayText}`);
      $('.budget-header-days-age', $displayElement).attr('title', `${i10n('budget.dob.outflow', 'Total outflow')}: ${formatCurrency(totalOutflow)}
${i10n('budget.dob.days', 'Total days of budgeting')}: ${totalDays}
${i10n('budget.dob.avgOutflow', 'Average daily outflow')}: ~${formatCurrency(averageDailyOutflow)}`);
    }
  }

  /**
   * Days of buffering is calculated by taking the total outflows within the selected window and
   * dividing it by the number of days in the selected window. The selected window comes from
   * the DaysOfBufferingHistoryLookup option and is represented in months. We estimate days to
   * be (number of months * 30). If the user selected "all history", the divisor is simply the
   * number of days since the first eligible transaction. If there are not at least 15 unique
   * days within the window that have an eligible transaction, we consider the metric unable to
   * be calculated.
   */
  _calculateDaysOfBuffering = (balance, transactions) => {
    const { dates, totalOutflow, uniqueDates } = transactions.reduce((reduced, current) => {
      const { amount, date } = current.getProperties('amount', 'date');
      reduced.dates.push(date.toUTCMoment());
      reduced.uniqueDates.set(date.format());
      reduced.totalOutflow += amount;
      return reduced;
    }, { dates: [], totalOutflow: 0, uniqueDates: new Map() });

    const minDate = moment.min(dates);
    const maxDate = moment.max(dates);
    const availableDates = maxDate.diff(minDate, 'days');

    let averageDailyOutflow;
    if (this._lookbackDays !== 0) {
      averageDailyOutflow = Math.abs(totalOutflow / this._lookbackDays);
    } else {
      averageDailyOutflow = Math.abs(totalOutflow / availableDates);
    }

    let daysOfBuffering = balance / averageDailyOutflow;
    if (daysOfBuffering < 10) {
      daysOfBuffering = daysOfBuffering.toFixed(1);
    } else {
      daysOfBuffering = Math.floor(daysOfBuffering);
    }

    const notEnoughDates = uniqueDates.size < 15;
    if (notEnoughDates) {
      daysOfBuffering = null;
    }

    return {
      averageDailyOutflow,
      daysOfBuffering,
      notEnoughDates,
      totalDays: uniqueDates.size,
      totalOutflow
    };
  }

  _eligibleTransactionFilter = (transaction) => {
    const today = new ynab.utilities.DateWithoutTime();

    let isEligibleDate = false;
    if (this._lookbackDays === 0) {
      isEligibleDate = true;
    } else {
      isEligibleDate = transaction.get('date').daysApart(today) < this._lookbackDays;
    }

    return (
      isEligibleDate &&
      !transaction.get('isTombstone') &&
      !transaction.get('payee.isInternal') &&
      !transaction.isTransferTransaction() &&
      transaction.get('account.onBudget') &&
      transaction.get('amount') < 0
    );
  }
}
<|endoftext|>


<commit_before>class ElevateWidgetStateMachine {
    _state = DISPLAY_DEFINITIONS.initialState;
    componentContext;

    transitionTo(nextState) {
        const currentStateDefinition = DISPLAY_DEFINITIONS[this._state];
        const destinationTransition = currentStateDefinition.transitions[nextState];
        if (!destinationTransition || this._state === nextState) return;

        const destinationState = destinationTransition.target;
        const destinationStateDefinition = DISPLAY_DEFINITIONS[destinationState];

        destinationTransition.action.call(this)
        currentStateDefinition.actions.onExit.call(this);
        destinationStateDefinition.actions.onEnter.call(this);

        this._state = destinationState;
    }

    currentState() {
        return this._state;
    }
}

const DISPLAY_DEFINITIONS = Object.freeze({
    initialState: 'loading',
    loading: {
        actions: {
            onEnter() {
                console.log('loading: onEnter');
            },
            onExit() {
                console.log('loading: onExit');
            },
        },
        transitions: {
            charge: {
                target: 'charge',
                action() {
                    console.log('transition action for "charge" from "loading" state');
                },
            },
            deactivated: {
                target: 'deactivated',
                action() {
                    console.log('transition action for "deactivated" from "loading" state');
                },
            },
            readOnly: {
                target: 'readOnly',
                action() {
                    console.log('transition action for "readOnly" from "loading" state');
                },
            }
        },
    },
    charge: {
        actions: {
            onEnter() {
                console.log('charge: onEnter');
                if (!this.componentContext.isMounted) {
                    console.log('turn loading on');
                    this.componentContext.loadingOn();
                }
            },
            onExit() {
                console.log('charge: onExit');
            },
        },
        transitions: {
            loading: {
                target: 'loading',
                action() {
                    console.log('transition action for "loading" from "charge" state');
                    // store previous values relevant to widget from formState
                },
            },
            saving: {
                target: 'saving',
                action() {
                    console.log('transition action for "saving" from "charge" state');
                    // store previous values relevant to widget from formState
                },
            },
            deactivated: {
                target: 'deactivated',
                action() {
                    console.log('transition action for "deactivated" from "charge" state');
                    // store previous values relevant to widget from formState
                },
            },
            userOriginatedDoNotCharge: {
                target: 'doNotCharge',
                action() {
                    console.log('transition action for "doNotCharge" from "charge" state');
                },
            },
            readOnly: {
                target: 'readOnly',
                action() {
                    console.log('transition action for "readOnly" from "charge" state');
                },
            },
            criticalError: {
                target: 'criticalError',
                action() {
                    console.log('transition action for "criticalerror" from "charge" state');
                },
            }
        },
    },
    deactivated: {
        actions: {
            onEnter() {
                console.log('deactivated: onEnter');
                this.componentContext.dismount();
            },
            onExit() {
                console.log('deactivated: onExit')
            },
        },
        transitions: {
            loading: {
                target: 'loading',
                action() {
                    console.log('transition action for "loading" from "deactivated" state');
                    // store previous values relevant to widget from formState
                },
            },
            charge: {
                target: 'charge',
                action() {
                    console.log('transition action for "charge" from "deactivated" state');
                },
            },
            readOnly: {
                target: 'readOnly',
                action() {
                    console.log('transition action for "readOnly" from "deactivated" state');
                },
            }
        },
    },
    doNotCharge: {
        actions: {
            onEnter() {
                console.log('doNotCharge: onEnter');
                this.componentContext.dismount();
            },
            onExit() {
                console.log('doNotCharge: onExit')
            },
        },
        transitions: {
            loading: {
                target: 'loading',
                action() {
                    console.log('transition action for "loading" from "doNotCharge" state');
                    // store previous values relevant to widget from formState
                },
            },
            userOriginatedCharge: {
                target: 'charge',
                action() {
                    console.log('transition action for "userOriginatedCharge" from "doNotCharge" state');
                },
            },
            deactivated: {
                target: 'deactivated',
                action() {
                    console.log('transition action for "deactivated" from "doNotCharge" state');
                },
            },
        },
    },
    readOnly: {
        actions: {
            onEnter() {
                console.log('readOnly: onEnter');
                this.componentContext.dismount();
            },
            onExit() {
                console.log('readOnly: onExit')
            },
        },
        transitions: {
            loading: {
                target: 'loading',
                action() {
                    console.log('transition action for "loading" from "readOnly" state');
                    // store previous values relevant to widget from formState
                },
            },
            editExpiredTransaction: {
                target: 'edit',
                action() {
                    console.log('transition action for "charge" from "readOnly" state, aka edit an expired transaction');
                }
            },
            // We shouldn't able to do this below
            userOriginatedEdit: {
                target: 'charge',
                action() {
                    console.log('transition action for "charge" from "readOnly" state, aka user wants to edit transaction');
                }
            },
            charge: {
                target: 'charge',
                action() {
                    console.log('transition action for "charge" from "readOnly" state');
                },
            },
            deactivated: {
                target: 'deactivated',
                action() {
                    console.log('transition action for "deactivated" from "readOnly" state');
                },
            },
        },
    },
    saving: {
        actions: {
            onEnter() {
                console.log('saving: onEnter');
                this.componentContext.dismount();
            },
            onExit() {
                console.log('saving: onExit')
            },
        },
        transitions: {
            loading: {
                target: 'loading',
                action() {
                    console.log('transition action for "loading" from "saving" state');
                },
            },
        }
    },
    criticalError: {
        actions: {
            onEnter() {
                console.log('criticalError: onEnter');
                this.componentContext.dismount();
            },
            onExit() {
                console.log('criticalError: onExit')
            },
        },
        transitions: {}
    },
    edit: {
        actions: {
            onEnter() {
                console.log('criticalError: onEnter');
                this.componentContext.dismount();
            },
            onExit() {
                console.log('criticalError: onExit')
            },
        },
        transitions: {
            readOnly: {
                target: 'readOnly',
                action() {
                    console.log('transition action for "readOnly" from "edit" state');
                },
            }
        }
    }
});

export default ElevateWidgetStateMachine;
<commit_msg>update class name<commit_after>class ElevateWidgetDisplay {
    _state = DISPLAY_DEFINITIONS.initialState;
    componentContext;

    transitionTo(nextState) {
        const currentStateDefinition = DISPLAY_DEFINITIONS[this._state];
        const destinationTransition = currentStateDefinition.transitions[nextState];
        if (!destinationTransition || this._state === nextState) return;

        const destinationState = destinationTransition.target;
        const destinationStateDefinition = DISPLAY_DEFINITIONS[destinationState];

        destinationTransition.action.call(this)
        currentStateDefinition.actions.onExit.call(this);
        destinationStateDefinition.actions.onEnter.call(this);

        this._state = destinationState;
    }

    currentState() {
        return this._state;
    }
}

const DISPLAY_DEFINITIONS = Object.freeze({
    initialState: 'loading',
    loading: {
        actions: {
            onEnter() {
                console.log('loading: onEnter');
            },
            onExit() {
                console.log('loading: onExit');
            },
        },
        transitions: {
            charge: {
                target: 'charge',
                action() {
                    console.log('transition action for "charge" from "loading" state');
                },
            },
            deactivated: {
                target: 'deactivated',
                action() {
                    console.log('transition action for "deactivated" from "loading" state');
                },
            },
            readOnly: {
                target: 'readOnly',
                action() {
                    console.log('transition action for "readOnly" from "loading" state');
                },
            }
        },
    },
    charge: {
        actions: {
            onEnter() {
                console.log('charge: onEnter');
                if (!this.componentContext.isMounted) {
                    console.log('turn loading on');
                    this.componentContext.loadingOn();
                }
            },
            onExit() {
                console.log('charge: onExit');
            },
        },
        transitions: {
            loading: {
                target: 'loading',
                action() {
                    console.log('transition action for "loading" from "charge" state');
                    // store previous values relevant to widget from formState
                },
            },
            saving: {
                target: 'saving',
                action() {
                    console.log('transition action for "saving" from "charge" state');
                    // store previous values relevant to widget from formState
                },
            },
            deactivated: {
                target: 'deactivated',
                action() {
                    console.log('transition action for "deactivated" from "charge" state');
                    // store previous values relevant to widget from formState
                },
            },
            userOriginatedDoNotCharge: {
                target: 'doNotCharge',
                action() {
                    console.log('transition action for "doNotCharge" from "charge" state');
                },
            },
            readOnly: {
                target: 'readOnly',
                action() {
                    console.log('transition action for "readOnly" from "charge" state');
                },
            },
            criticalError: {
                target: 'criticalError',
                action() {
                    console.log('transition action for "criticalerror" from "charge" state');
                },
            }
        },
    },
    deactivated: {
        actions: {
            onEnter() {
                console.log('deactivated: onEnter');
                this.componentContext.dismount();
            },
            onExit() {
                console.log('deactivated: onExit')
            },
        },
        transitions: {
            loading: {
                target: 'loading',
                action() {
                    console.log('transition action for "loading" from "deactivated" state');
                    // store previous values relevant to widget from formState
                },
            },
            charge: {
                target: 'charge',
                action() {
                    console.log('transition action for "charge" from "deactivated" state');
                },
            },
            readOnly: {
                target: 'readOnly',
                action() {
                    console.log('transition action for "readOnly" from "deactivated" state');
                },
            }
        },
    },
    doNotCharge: {
        actions: {
            onEnter() {
                console.log('doNotCharge: onEnter');
                this.componentContext.dismount();
            },
            onExit() {
                console.log('doNotCharge: onExit')
            },
        },
        transitions: {
            loading: {
                target: 'loading',
                action() {
                    console.log('transition action for "loading" from "doNotCharge" state');
                    // store previous values relevant to widget from formState
                },
            },
            userOriginatedCharge: {
                target: 'charge',
                action() {
                    console.log('transition action for "userOriginatedCharge" from "doNotCharge" state');
                },
            },
            deactivated: {
                target: 'deactivated',
                action() {
                    console.log('transition action for "deactivated" from "doNotCharge" state');
                },
            },
        },
    },
    readOnly: {
        actions: {
            onEnter() {
                console.log('readOnly: onEnter');
                this.componentContext.dismount();
            },
            onExit() {
                console.log('readOnly: onExit')
            },
        },
        transitions: {
            loading: {
                target: 'loading',
                action() {
                    console.log('transition action for "loading" from "readOnly" state');
                    // store previous values relevant to widget from formState
                },
            },
            editExpiredTransaction: {
                target: 'edit',
                action() {
                    console.log('transition action for "charge" from "readOnly" state, aka edit an expired transaction');
                }
            },
            // We shouldn't able to do this below
            userOriginatedEdit: {
                target: 'charge',
                action() {
                    console.log('transition action for "charge" from "readOnly" state, aka user wants to edit transaction');
                }
            },
            charge: {
                target: 'charge',
                action() {
                    console.log('transition action for "charge" from "readOnly" state');
                },
            },
            deactivated: {
                target: 'deactivated',
                action() {
                    console.log('transition action for "deactivated" from "readOnly" state');
                },
            },
        },
    },
    saving: {
        actions: {
            onEnter() {
                console.log('saving: onEnter');
                this.componentContext.dismount();
            },
            onExit() {
                console.log('saving: onExit')
            },
        },
        transitions: {
            loading: {
                target: 'loading',
                action() {
                    console.log('transition action for "loading" from "saving" state');
                },
            },
        }
    },
    criticalError: {
        actions: {
            onEnter() {
                console.log('criticalError: onEnter');
                this.componentContext.dismount();
            },
            onExit() {
                console.log('criticalError: onExit')
            },
        },
        transitions: {}
    },
    edit: {
        actions: {
            onEnter() {
                console.log('criticalError: onEnter');
                this.componentContext.dismount();
            },
            onExit() {
                console.log('criticalError: onExit')
            },
        },
        transitions: {
            readOnly: {
                target: 'readOnly',
                action() {
                    console.log('transition action for "readOnly" from "edit" state');
                },
            }
        }
    }
});

export default ElevateWidgetDisplay;
<|endoftext|>


<commit_before>// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.

define([
    "widgets/js/widget",
    "base/js/utils",
    "jquery",
    "bootstrap",
], function(widget, utils, $){

    var DropdownView = widget.DOMWidgetView.extend({
        render : function(){
            /**
             * Called when view is rendered.
             */
            this.$el
                .addClass('widget-hbox widget-dropdown');
            this.$label = $('<div />')
                .appendTo(this.$el)
                .addClass('widget-label')
                .hide();
            this.$buttongroup = $('<div />')
                .addClass('widget_item')
                .addClass('btn-group')
                .appendTo(this.$el);
            this.$droplabel = $('<button />')
                .addClass('btn btn-default')
                .addClass('widget-combo-btn')
                .html(" ")
                .appendTo(this.$buttongroup);
            this.$dropbutton = $('<button />')
                .addClass('btn btn-default')
                .addClass('dropdown-toggle')
                .addClass('widget-combo-carrot-btn')
                .attr('data-toggle', 'dropdown')
                .append($('<span />').addClass("caret"))
                .appendTo(this.$buttongroup);
            this.$droplist = $('<ul />')
                .addClass('dropdown-menu')
                .appendTo(this.$buttongroup);

            this.model.on('change:button_style', function(model, value) {
                this.update_button_style();
            }, this);
            this.update_button_style('');

            // Set defaults.
            this.update();
        },

        update : function(options){
            /**
             * Update the contents of this view
             *
             * Called when the model is changed.  The model may have been
             * changed by another view or by a state update from the back-end.
             */

            if (options === undefined || options.updated_view != this) {
                var selected_item_text = this.model.get('value_name');
                if (selected_item_text.trim().length === 0) {
                    this.$droplabel.html(" ");
                } else {
                    this.$droplabel.text(selected_item_text);
                }

                var items = this.model.get('value_names');
                var $replace_droplist = $('<ul />')
                    .addClass('dropdown-menu');
                // Copy the style
                $replace_droplist.attr('style', this.$droplist.attr('style'));
                var that = this;
                _.each(items, function(item, i) {
                    var item_button = $('<a href="#"/>')
                        .text(item)
                        .on('click', $.proxy(that.handle_click, that));
                    $replace_droplist.append($('<li />').append(item_button));
                });

                this.$droplist.replaceWith($replace_droplist);
                this.$droplist.remove();
                this.$droplist = $replace_droplist;

                if (this.model.get('disabled')) {
                    this.$buttongroup.attr('disabled','disabled');
                    this.$droplabel.attr('disabled','disabled');
                    this.$dropbutton.attr('disabled','disabled');
                    this.$droplist.attr('disabled','disabled');
                } else {
                    this.$buttongroup.removeAttr('disabled');
                    this.$droplabel.removeAttr('disabled');
                    this.$dropbutton.removeAttr('disabled');
                    this.$droplist.removeAttr('disabled');
                }

                var description = this.model.get('description');
                if (description.length === 0) {
                    this.$label.hide();
                } else {
                    this.typeset(this.$label, description);
                    this.$label.show();
                }
            }
            return DropdownView.__super__.update.apply(this);
        },

        update_button_style: function(previous_trait_value) {
            var class_map = {
                primary: ['btn-primary'],
                success: ['btn-success'],
                info: ['btn-info'],
                warning: ['btn-warning'],
                danger: ['btn-danger']
            };
            this.update_mapped_classes(class_map, 'button_style', previous_trait_value, this.$droplabel);
            this.update_mapped_classes(class_map, 'button_style', previous_trait_value, this.$dropbutton);
        },

        update_attr: function(name, value) {
            /**
             * Set a css attr of the widget view.
             */
            if (name.substring(0, 6) == 'border' || name == 'background' || name == 'color') {
                this.$droplabel.css(name, value);
                this.$dropbutton.css(name, value);
                this.$droplist.css(name, value);
            } else if (name == 'width') {
                this.$droplist.css(name, value);
                this.$droplabel.css(name, value);
            } else if (name == 'padding') {
                this.$droplist.css(name, value);
                this.$buttongroup.css(name, value);
            } else if (name == 'margin') {
                this.$buttongroup.css(name, value);
            } else if (name == 'height') {
                this.$droplabel.css(name, value);
                this.$dropbutton.css(name, value);
            } else {
                this.$droplist.css(name, value);
                this.$droplabel.css(name, value);
            }
        },

        handle_click: function (e) {
            /**
             * Handle when a value is clicked.
             *
             * Calling model.set will trigger all of the other views of the
             * model to update.
             */
            this.model.set('value_name', $(e.target).text(), {updated_view: this});
            this.touch();
        },

    });

    var RadioButtonsView = widget.DOMWidgetView.extend({
        render : function(){
            /**
             * Called when view is rendered.
             */
            this.$el
                .addClass('widget-hbox widget-radio');
            this.$label = $('<div />')
                .appendTo(this.$el)
                .addClass('widget-label')
                .hide();
            this.$container = $('<div />')
                .appendTo(this.$el)
                .addClass('widget-radio-box');
            this.update();
        },

        update : function(options){
            /**
             * Update the contents of this view
             *
             * Called when the model is changed.  The model may have been
             * changed by another view or by a state update from the back-end.
             */
            if (options === undefined || options.updated_view != this) {
                // Add missing items to the DOM.
                var items = this.model.get('value_names');
                var disabled = this.model.get('disabled');
                var that = this;
                _.each(items, function(item, index) {
                    var item_query = ' :input[value="' + item + '"]';
                    if (that.$el.find(item_query).length === 0) {
                        var $label = $('<label />')
                            .addClass('radio')
                            .text(item)
                            .appendTo(that.$container);

                        $('<input />')
                            .attr('type', 'radio')
                            .addClass(that.model)
                            .val(item)
                            .prependTo($label)
                            .on('click', $.proxy(that.handle_click, that));
                    }

                    var $item_element = that.$container.find(item_query);
                    if (that.model.get('value_name') == item) {
                        $item_element.prop('checked', true);
                    } else {
                        $item_element.prop('checked', false);
                    }
                    $item_element.prop('disabled', disabled);
                });

                // Remove items that no longer exist.
                this.$container.find('input').each(function(i, obj) {
                    var value = $(obj).val();
                    var found = false;
                    _.each(items, function(item, index) {
                        if (item == value) {
                            found = true;
                            return false;
                        }
                    });

                    if (!found) {
                        $(obj).parent().remove();
                    }
                });

                var description = this.model.get('description');
                if (description.length === 0) {
                    this.$label.hide();
                } else {
                    this.$label.text(description);
                    this.typeset(this.$label, description);
                    this.$label.show();
                }
            }
            return RadioButtonsView.__super__.update.apply(this);
        },

        update_attr: function(name, value) {
            /**
             * Set a css attr of the widget view.
             */
            this.$container.css(name, value);
        },

        handle_click: function (e) {
            /**
             * Handle when a value is clicked.
             *
             * Calling model.set will trigger all of the other views of the
             * model to update.
             */
            this.model.set('value_name', $(e.target).val(), {updated_view: this});
            this.touch();
        },
    });

    var ToggleButtonsView = widget.DOMWidgetView.extend({
        initialize: function() {
            this._css_state = {};
            ToggleButtonsView.__super__.initialize.apply(this, arguments);
        },

        render: function() {
            /**
             * Called when view is rendered.
             */
            this.$el
                .addClass('widget-hbox widget-toggle-buttons');
            this.$label = $('<div />')
                .appendTo(this.$el)
                .addClass('widget-label')
                .hide();
            this.$buttongroup = $('<div />')
                .addClass('btn-group')
                .attr('data-toggle', 'buttons-radio')
                .appendTo(this.$el);

            this.model.on('change:button_style', function(model, value) {
                this.update_button_style();
            }, this);
            this.update_button_style('');
            this.update();
        },

        update : function(options){
            /**
             * Update the contents of this view
             *
             * Called when the model is changed.  The model may have been
             * changed by another view or by a state update from the back-end.
             */
            if (options === undefined || options.updated_view != this) {
                // Add missing items to the DOM.
                var items = this.model.get('value_names');
                var disabled = this.model.get('disabled');
                var that = this;
                var item_html;
                _.each(items, function(item, index) {
                    if (item.trim().length == 0) {
                        item_html = " ";
                    } else {
                        item_html = utils.escape_html(item);
                    }
                    var item_query = '[data-value="' + item + '"]';
                    var $item_element = that.$buttongroup.find(item_query);
                    if (!$item_element.length) {
                        $item_element = $('<button/>')
                            .attr('type', 'button')
                            .addClass('btn btn-default')
                            .html(item_html)
                            .appendTo(that.$buttongroup)
                            .attr('data-value', item)
                            .on('click', $.proxy(that.handle_click, that));
                        that.update_style_traits($item_element);
                    }
                    if (that.model.get('value_name') == item) {
                        $item_element.addClass('active');
                    } else {
                        $item_element.removeClass('active');
                    }
                    $item_element.prop('disabled', disabled);
                });

                // Remove items that no longer exist.
                this.$buttongroup.find('button').each(function(i, obj) {
                    var value = $(obj).data('value');
                    var found = false;
                    _.each(items, function(item, index) {
                        if (item == value) {
                            found = true;
                            return false;
                        }
                    });

                    if (!found) {
                        $(obj).remove();
                    }
                });

                var description = this.model.get('description');
                if (description.length === 0) {
                    this.$label.hide();
                } else {
                    this.$label.text();
                    this.typeset(this.$label, description);
                    this.$label.show();
                }
            }
            return ToggleButtonsView.__super__.update.apply(this);
        },

        update_attr: function(name, value) {
            /**
             * Set a css attr of the widget view.
             */
            this._css_state[name] = value;
            this.update_style_traits();
        },

        update_style_traits: function(button) {
            for (var name in this._css_state) {
                if (this._css_state.hasOwnProperty(name)) {
                    if (name == 'margin') {
                        this.$buttongroup.css(name, this._css_state[name]);
                    } else if (name != 'width') {
                        if (button) {
                            button.css(name, this._css_state[name]);
                        } else {
                            this.$buttongroup.find('button').css(name, this._css_state[name]);
                        }
                    }
                }
            }
        },

        update_button_style: function(previous_trait_value) {
            var class_map = {
                primary: ['btn-primary'],
                success: ['btn-success'],
                info: ['btn-info'],
                warning: ['btn-warning'],
                danger: ['btn-danger']
            };
            this.update_mapped_classes(class_map, 'button_style', previous_trait_value, this.$buttongroup.find('button'));
        },

        handle_click: function (e) {
            /**
             * Handle when a value is clicked.
             *
             * Calling model.set will trigger all of the other views of the
             * model to update.
             */
            this.model.set('value_name', $(e.target).data('value'), {updated_view: this});
            this.touch();
        },
    });

    var SelectView = widget.DOMWidgetView.extend({
        render : function(){
            /**
             * Called when view is rendered.
             */
            this.$el
                .addClass('widget-hbox widget-select');
            this.$label = $('<div />')
                .appendTo(this.$el)
                .addClass('widget-label')
                .hide();
            this.$listbox = $('<select />')
                .addClass('widget-listbox form-control')
                .attr('size', 6)
                .appendTo(this.$el);
            this.update();
        },

        update : function(options){
            /**
             * Update the contents of this view
             *
             * Called when the model is changed.  The model may have been
             * changed by another view or by a state update from the back-end.
             */
            if (options === undefined || options.updated_view != this) {
                // Add missing items to the DOM.
                var items = this.model.get('value_names');
                var that = this;
                _.each(items, function(item, index) {
                   var item_query = 'option[value_name="' + item + '"]';
                    if (that.$listbox.find(item_query).length === 0) {
                        $('<option />')
                            .text(item)
                            .attr('value_name', item)
                            .appendTo(that.$listbox)
                            .on('click', $.proxy(that.handle_click, that));
                    }
                });

                // Select the correct element
                this.$listbox.val(this.model.get('value_name'));

                // Disable listbox if needed
                var disabled = this.model.get('disabled');
                this.$listbox.prop('disabled', disabled);

                // Remove items that no longer exist.
                this.$listbox.find('option').each(function(i, obj) {
                    var value = $(obj).text();
                    var found = false;
                    _.each(items, function(item, index) {
                        if (item == value) {
                            found = true;
                            return false;
                        }
                    });

                    if (!found) {
                        $(obj).remove();
                    }
                });

                var description = this.model.get('description');
                if (description.length === 0) {
                    this.$label.hide();
                } else {
                    this.typeset(this.$label, description);
                    this.$label.show();
                }
            }
            return SelectView.__super__.update.apply(this);
        },

        update_attr: function(name, value) {
            /**
             * Set a css attr of the widget view.
             */
            this.$listbox.css(name, value);
        },

        handle_click: function (e) {
            /**
             * Handle when a value is clicked.
             *
             * Calling model.set will trigger all of the other views of the
             * model to update.
             */
            this.model.set('value_name', $(e.target).text(), {updated_view: this});
            this.touch();
        },
    });

    return {
        'DropdownView': DropdownView,
        'RadioButtonsView': RadioButtonsView,
        'ToggleButtonsView': ToggleButtonsView,
        'SelectView': SelectView,
    };
});
<commit_msg>Fix bug where selection box modification would cause page to scroll to the top<commit_after>// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.

define([
    "widgets/js/widget",
    "base/js/utils",
    "jquery",
    "bootstrap",
], function(widget, utils, $){

    var DropdownView = widget.DOMWidgetView.extend({
        render : function(){
            /**
             * Called when view is rendered.
             */
            this.$el
                .addClass('widget-hbox widget-dropdown');
            this.$label = $('<div />')
                .appendTo(this.$el)
                .addClass('widget-label')
                .hide();
            this.$buttongroup = $('<div />')
                .addClass('widget_item')
                .addClass('btn-group')
                .appendTo(this.$el);
            this.$droplabel = $('<button />')
                .addClass('btn btn-default')
                .addClass('widget-combo-btn')
                .html(" ")
                .appendTo(this.$buttongroup);
            this.$dropbutton = $('<button />')
                .addClass('btn btn-default')
                .addClass('dropdown-toggle')
                .addClass('widget-combo-carrot-btn')
                .attr('data-toggle', 'dropdown')
                .append($('<span />').addClass("caret"))
                .appendTo(this.$buttongroup);
            this.$droplist = $('<ul />')
                .addClass('dropdown-menu')
                .appendTo(this.$buttongroup);

            this.model.on('change:button_style', function(model, value) {
                this.update_button_style();
            }, this);
            this.update_button_style('');

            // Set defaults.
            this.update();
        },

        update : function(options){
            /**
             * Update the contents of this view
             *
             * Called when the model is changed.  The model may have been
             * changed by another view or by a state update from the back-end.
             */

            if (options === undefined || options.updated_view != this) {
                var selected_item_text = this.model.get('value_name');
                if (selected_item_text.trim().length === 0) {
                    this.$droplabel.html(" ");
                } else {
                    this.$droplabel.text(selected_item_text);
                }

                var items = this.model.get('value_names');
                var $replace_droplist = $('<ul />')
                    .addClass('dropdown-menu');
                // Copy the style
                $replace_droplist.attr('style', this.$droplist.attr('style'));
                var that = this;
                _.each(items, function(item, i) {
                    var item_button = $('<a href="#"/>')
                        .text(item)
                        .on('click', $.proxy(that.handle_click, that));
                    $replace_droplist.append($('<li />').append(item_button));
                });

                this.$droplist.replaceWith($replace_droplist);
                this.$droplist.remove();
                this.$droplist = $replace_droplist;

                if (this.model.get('disabled')) {
                    this.$buttongroup.attr('disabled','disabled');
                    this.$droplabel.attr('disabled','disabled');
                    this.$dropbutton.attr('disabled','disabled');
                    this.$droplist.attr('disabled','disabled');
                } else {
                    this.$buttongroup.removeAttr('disabled');
                    this.$droplabel.removeAttr('disabled');
                    this.$dropbutton.removeAttr('disabled');
                    this.$droplist.removeAttr('disabled');
                }

                var description = this.model.get('description');
                if (description.length === 0) {
                    this.$label.hide();
                } else {
                    this.typeset(this.$label, description);
                    this.$label.show();
                }
            }
            return DropdownView.__super__.update.apply(this);
        },

        update_button_style: function(previous_trait_value) {
            var class_map = {
                primary: ['btn-primary'],
                success: ['btn-success'],
                info: ['btn-info'],
                warning: ['btn-warning'],
                danger: ['btn-danger']
            };
            this.update_mapped_classes(class_map, 'button_style', previous_trait_value, this.$droplabel);
            this.update_mapped_classes(class_map, 'button_style', previous_trait_value, this.$dropbutton);
        },

        update_attr: function(name, value) {
            /**
             * Set a css attr of the widget view.
             */
            if (name.substring(0, 6) == 'border' || name == 'background' || name == 'color') {
                this.$droplabel.css(name, value);
                this.$dropbutton.css(name, value);
                this.$droplist.css(name, value);
            } else if (name == 'width') {
                this.$droplist.css(name, value);
                this.$droplabel.css(name, value);
            } else if (name == 'padding') {
                this.$droplist.css(name, value);
                this.$buttongroup.css(name, value);
            } else if (name == 'margin') {
                this.$buttongroup.css(name, value);
            } else if (name == 'height') {
                this.$droplabel.css(name, value);
                this.$dropbutton.css(name, value);
            } else {
                this.$droplist.css(name, value);
                this.$droplabel.css(name, value);
            }
        },

        handle_click: function (e) {
            /**
             * Handle when a value is clicked.
             *
             * Calling model.set will trigger all of the other views of the
             * model to update.
             */
            this.model.set('value_name', $(e.target).text(), {updated_view: this});
            this.touch();

            // Manually hide the droplist.
            e.stopPropagation();
            e.preventDefault();
            this.$buttongroup.removeClass('open');
        },

    });

    var RadioButtonsView = widget.DOMWidgetView.extend({
        render : function(){
            /**
             * Called when view is rendered.
             */
            this.$el
                .addClass('widget-hbox widget-radio');
            this.$label = $('<div />')
                .appendTo(this.$el)
                .addClass('widget-label')
                .hide();
            this.$container = $('<div />')
                .appendTo(this.$el)
                .addClass('widget-radio-box');
            this.update();
        },

        update : function(options){
            /**
             * Update the contents of this view
             *
             * Called when the model is changed.  The model may have been
             * changed by another view or by a state update from the back-end.
             */
            if (options === undefined || options.updated_view != this) {
                // Add missing items to the DOM.
                var items = this.model.get('value_names');
                var disabled = this.model.get('disabled');
                var that = this;
                _.each(items, function(item, index) {
                    var item_query = ' :input[value="' + item + '"]';
                    if (that.$el.find(item_query).length === 0) {
                        var $label = $('<label />')
                            .addClass('radio')
                            .text(item)
                            .appendTo(that.$container);

                        $('<input />')
                            .attr('type', 'radio')
                            .addClass(that.model)
                            .val(item)
                            .prependTo($label)
                            .on('click', $.proxy(that.handle_click, that));
                    }

                    var $item_element = that.$container.find(item_query);
                    if (that.model.get('value_name') == item) {
                        $item_element.prop('checked', true);
                    } else {
                        $item_element.prop('checked', false);
                    }
                    $item_element.prop('disabled', disabled);
                });

                // Remove items that no longer exist.
                this.$container.find('input').each(function(i, obj) {
                    var value = $(obj).val();
                    var found = false;
                    _.each(items, function(item, index) {
                        if (item == value) {
                            found = true;
                            return false;
                        }
                    });

                    if (!found) {
                        $(obj).parent().remove();
                    }
                });

                var description = this.model.get('description');
                if (description.length === 0) {
                    this.$label.hide();
                } else {
                    this.$label.text(description);
                    this.typeset(this.$label, description);
                    this.$label.show();
                }
            }
            return RadioButtonsView.__super__.update.apply(this);
        },

        update_attr: function(name, value) {
            /**
             * Set a css attr of the widget view.
             */
            this.$container.css(name, value);
        },

        handle_click: function (e) {
            /**
             * Handle when a value is clicked.
             *
             * Calling model.set will trigger all of the other views of the
             * model to update.
             */
            this.model.set('value_name', $(e.target).val(), {updated_view: this});
            this.touch();
        },
    });

    var ToggleButtonsView = widget.DOMWidgetView.extend({
        initialize: function() {
            this._css_state = {};
            ToggleButtonsView.__super__.initialize.apply(this, arguments);
        },

        render: function() {
            /**
             * Called when view is rendered.
             */
            this.$el
                .addClass('widget-hbox widget-toggle-buttons');
            this.$label = $('<div />')
                .appendTo(this.$el)
                .addClass('widget-label')
                .hide();
            this.$buttongroup = $('<div />')
                .addClass('btn-group')
                .attr('data-toggle', 'buttons-radio')
                .appendTo(this.$el);

            this.model.on('change:button_style', function(model, value) {
                this.update_button_style();
            }, this);
            this.update_button_style('');
            this.update();
        },

        update : function(options){
            /**
             * Update the contents of this view
             *
             * Called when the model is changed.  The model may have been
             * changed by another view or by a state update from the back-end.
             */
            if (options === undefined || options.updated_view != this) {
                // Add missing items to the DOM.
                var items = this.model.get('value_names');
                var disabled = this.model.get('disabled');
                var that = this;
                var item_html;
                _.each(items, function(item, index) {
                    if (item.trim().length == 0) {
                        item_html = " ";
                    } else {
                        item_html = utils.escape_html(item);
                    }
                    var item_query = '[data-value="' + item + '"]';
                    var $item_element = that.$buttongroup.find(item_query);
                    if (!$item_element.length) {
                        $item_element = $('<button/>')
                            .attr('type', 'button')
                            .addClass('btn btn-default')
                            .html(item_html)
                            .appendTo(that.$buttongroup)
                            .attr('data-value', item)
                            .on('click', $.proxy(that.handle_click, that));
                        that.update_style_traits($item_element);
                    }
                    if (that.model.get('value_name') == item) {
                        $item_element.addClass('active');
                    } else {
                        $item_element.removeClass('active');
                    }
                    $item_element.prop('disabled', disabled);
                });

                // Remove items that no longer exist.
                this.$buttongroup.find('button').each(function(i, obj) {
                    var value = $(obj).data('value');
                    var found = false;
                    _.each(items, function(item, index) {
                        if (item == value) {
                            found = true;
                            return false;
                        }
                    });

                    if (!found) {
                        $(obj).remove();
                    }
                });

                var description = this.model.get('description');
                if (description.length === 0) {
                    this.$label.hide();
                } else {
                    this.$label.text();
                    this.typeset(this.$label, description);
                    this.$label.show();
                }
            }
            return ToggleButtonsView.__super__.update.apply(this);
        },

        update_attr: function(name, value) {
            /**
             * Set a css attr of the widget view.
             */
            this._css_state[name] = value;
            this.update_style_traits();
        },

        update_style_traits: function(button) {
            for (var name in this._css_state) {
                if (this._css_state.hasOwnProperty(name)) {
                    if (name == 'margin') {
                        this.$buttongroup.css(name, this._css_state[name]);
                    } else if (name != 'width') {
                        if (button) {
                            button.css(name, this._css_state[name]);
                        } else {
                            this.$buttongroup.find('button').css(name, this._css_state[name]);
                        }
                    }
                }
            }
        },

        update_button_style: function(previous_trait_value) {
            var class_map = {
                primary: ['btn-primary'],
                success: ['btn-success'],
                info: ['btn-info'],
                warning: ['btn-warning'],
                danger: ['btn-danger']
            };
            this.update_mapped_classes(class_map, 'button_style', previous_trait_value, this.$buttongroup.find('button'));
        },

        handle_click: function (e) {
            /**
             * Handle when a value is clicked.
             *
             * Calling model.set will trigger all of the other views of the
             * model to update.
             */
            this.model.set('value_name', $(e.target).data('value'), {updated_view: this});
            this.touch();
        },
    });

    var SelectView = widget.DOMWidgetView.extend({
        render : function(){
            /**
             * Called when view is rendered.
             */
            this.$el
                .addClass('widget-hbox widget-select');
            this.$label = $('<div />')
                .appendTo(this.$el)
                .addClass('widget-label')
                .hide();
            this.$listbox = $('<select />')
                .addClass('widget-listbox form-control')
                .attr('size', 6)
                .appendTo(this.$el);
            this.update();
        },

        update : function(options){
            /**
             * Update the contents of this view
             *
             * Called when the model is changed.  The model may have been
             * changed by another view or by a state update from the back-end.
             */
            if (options === undefined || options.updated_view != this) {
                // Add missing items to the DOM.
                var items = this.model.get('value_names');
                var that = this;
                _.each(items, function(item, index) {
                   var item_query = 'option[value_name="' + item + '"]';
                    if (that.$listbox.find(item_query).length === 0) {
                        $('<option />')
                            .text(item)
                            .attr('value_name', item)
                            .appendTo(that.$listbox)
                            .on('click', $.proxy(that.handle_click, that));
                    }
                });

                // Select the correct element
                this.$listbox.val(this.model.get('value_name'));

                // Disable listbox if needed
                var disabled = this.model.get('disabled');
                this.$listbox.prop('disabled', disabled);

                // Remove items that no longer exist.
                this.$listbox.find('option').each(function(i, obj) {
                    var value = $(obj).text();
                    var found = false;
                    _.each(items, function(item, index) {
                        if (item == value) {
                            found = true;
                            return false;
                        }
                    });

                    if (!found) {
                        $(obj).remove();
                    }
                });

                var description = this.model.get('description');
                if (description.length === 0) {
                    this.$label.hide();
                } else {
                    this.typeset(this.$label, description);
                    this.$label.show();
                }
            }
            return SelectView.__super__.update.apply(this);
        },

        update_attr: function(name, value) {
            /**
             * Set a css attr of the widget view.
             */
            this.$listbox.css(name, value);
        },

        handle_click: function (e) {
            /**
             * Handle when a value is clicked.
             *
             * Calling model.set will trigger all of the other views of the
             * model to update.
             */
            this.model.set('value_name', $(e.target).text(), {updated_view: this});
            this.touch();
        },
    });

    return {
        'DropdownView': DropdownView,
        'RadioButtonsView': RadioButtonsView,
        'ToggleButtonsView': ToggleButtonsView,
        'SelectView': SelectView,
    };
});
<|endoftext|>


<commit_before>module.exports = class DependenciesBuilderTest extends TestCase
{
	/** @test */
	building_an_object_with_type_hinted_dependencies()
	{
		global.Foo = Foo;
		global.Bar = Bar;
		global.Baz = Baz;

		let foo = DependenciesBuilder.build(Foo);

		this.assertEquals(2, foo.number);
		this.assertEquals(new Foo(new Bar(new Baz)), foo);
		this.assertEquals(new Bar(new Baz), foo.bar);
		this.assertEquals(new Baz, foo.bar.baz);

		this.assertEquals('foo', foo.say());
		this.assertEquals('bar', foo.bar.say());
		this.assertEquals('baz', foo.bar.baz.say());
	}

	/** @test */
	resolving_dependencies_for_a_given_es6_closure()
	{
		global.Foo = Foo;
		global.Bar = Bar;
		global.Baz = Baz;

		let closure = (/*Foo*/ foo) => {
			return foo;
		};

		let depencency = DependenciesBuilder.resolve(closure);

		this.assertEquals(new Foo(new Bar(new Baz)), depencency);
	}

	/** @test */
	resolving_dependencies_for_a_given_es5_closure()
	{
		global.Foo = Foo;
		global.Bar = Bar;
		global.Baz = Baz;

		let closure = function(/*Foo*/ foo) {
			return foo;
		};

		let depencency = DependenciesBuilder.resolve(closure);

		this.assertEquals(new Foo(new Bar(new Baz)), depencency);
	}
}

class Foo
{
	constructor(/*Bar*/ bar, number = 2)
	{
		this.bar = bar;
		this.number = number;
	}

	say()
	{
		return 'foo';
	}
}

class Bar
{
	constructor(/*Baz*/ baz)
	{
		this.baz = baz;
	}

	say()
	{
		return 'bar';
	}
}

class Baz
{
	say()
	{
		return 'baz';
	}
}
<commit_msg>Fix typo<commit_after>module.exports = class DependenciesBuilderTest extends TestCase
{
	/** @test */
	building_an_object_with_type_hinted_dependencies()
	{
		global.Foo = Foo;
		global.Bar = Bar;
		global.Baz = Baz;

		let foo = DependenciesBuilder.build(Foo);

		this.assertEquals(2, foo.number);
		this.assertEquals(new Foo(new Bar(new Baz)), foo);
		this.assertEquals(new Bar(new Baz), foo.bar);
		this.assertEquals(new Baz, foo.bar.baz);

		this.assertEquals('foo', foo.say());
		this.assertEquals('bar', foo.bar.say());
		this.assertEquals('baz', foo.bar.baz.say());
	}

	/** @test */
	resolving_dependencies_for_a_given_es6_closure()
	{
		global.Foo = Foo;
		global.Bar = Bar;
		global.Baz = Baz;

		let closure = (/*Foo*/ foo) => {
			return foo;
		};

		let dependency = DependenciesBuilder.resolve(closure);

		this.assertEquals(new Foo(new Bar(new Baz)), dependency);
	}

	/** @test */
	resolving_dependencies_for_a_given_es5_closure()
	{
		global.Foo = Foo;
		global.Bar = Bar;
		global.Baz = Baz;

		let closure = function(/*Foo*/ foo) {
			return foo;
		};

		let dependency = DependenciesBuilder.resolve(closure);

		this.assertEquals(new Foo(new Bar(new Baz)), dependency);
	}
}

class Foo
{
	constructor(/*Bar*/ bar, number = 2)
	{
		this.bar = bar;
		this.number = number;
	}

	say()
	{
		return 'foo';
	}
}

class Bar
{
	constructor(/*Baz*/ baz)
	{
		this.baz = baz;
	}

	say()
	{
		return 'bar';
	}
}

class Baz
{
	say()
	{
		return 'baz';
	}
}
<|endoftext|>