<commit_before>/**
 * @ngdoc directive
 * @name mdTabs
 * @module material.components.tabs
 *
 * @restrict E
 *
 * @description
 * The `<md-tabs>` directive serves as the container for 1..n `<md-tab>` child directives to produces a Tabs components.
 * In turn, the nested `<md-tab>` directive is used to specify a tab label for the **header button** and a [optional] tab view
 * content that will be associated with each tab button.
 *
 * Below is the markup for its simplest usage:
 *
 *  <hljs lang="html">
 *  <md-tabs>
 *    <md-tab label="Tab #1"></md-tab>
 *    <md-tab label="Tab #2"></md-tab>
 *    <md-tab label="Tab #3"></md-tab>
 *  </md-tabs>
 *  </hljs>
 *
 * Tabs supports three (3) usage scenarios:
 *
 *  1. Tabs (buttons only)
 *  2. Tabs with internal view content
 *  3. Tabs with external view content
 *
 * **Tab-only** support is useful when tab buttons are used for custom navigation regardless of any other components, content, or views.
 * **Tabs with internal views** are the traditional usages where each tab has associated view content and the view switching is managed internally by the Tabs component.
 * **Tabs with external view content** is often useful when content associated with each tab is independently managed and data-binding notifications announce tab selection changes.
 *
 * Additional features also include:
 *
 * *  Content can include any markup.
 * *  If a tab is disabled while active/selected, then the next tab will be auto-selected.
 *
 * ### Explanation of tab stretching
 *
 * Initially, tabs will have an inherent size.  This size will either be defined by how much space is needed to accommodate their text or set by the user through CSS.  Calculations will be based on this size.
 *
 * On mobile devices, tabs will be expanded to fill the available horizontal space.  When this happens, all tabs will become the same size.
 *
 * On desktops, by default, stretching will never occur.
 *
 * This default behavior can be overridden through the `md-stretch-tabs` attribute.  Here is a table showing when stretching will occur:
 *
 * `md-stretch-tabs` | mobile    | desktop
 * ------------------|-----------|--------
 * `auto`            | stretched | ---
 * `always`          | stretched | stretched
 * `never`           | ---       | ---
 *
 * @param {integer=} md-selected Index of the active/selected tab
 * @param {boolean=} md-no-ink If present, disables ink ripple effects.
 * @param {boolean=} md-no-ink-bar If present, disables the selection ink bar.
 * @param {string=}  md-align-tabs Attribute to indicate position of tab buttons: `bottom` or `top`; default is `top`
 * @param {string=} md-stretch-tabs Attribute to indicate whether or not to stretch tabs: `auto`, `always`, or `never`; default is `auto`
 * @param {boolean=} md-dynamic-height When enabled, the tab wrapper will resize based on the contents of the selected tab
 * @param {boolean=} md-center-tabs When enabled, tabs will be centered provided there is no need for pagination
 * @param {boolean=} md-no-pagination When enabled, pagination will remain off
 * @param {boolean=} md-swipe-content When enabled, swipe gestures will be enabled for the content area to jump between tabs
 * @param {boolean=} md-enable-disconnect When enabled, scopes will be disconnected for tabs that are not being displayed.  This provides a performance boost, but may also cause unexpected issues and is not recommended for most users.
 * @param {boolean=} md-autoselect When present, any tabs added after the initial load will be automatically selected
 *
 * @usage
 * <hljs lang="html">
 * <md-tabs md-selected="selectedIndex" >
 *
 *   <md-tab
 *       ng-repeat="tab in tabs | orderBy:predicate:reversed"
 *       md-on-select="onTabSelected(tab)"
 *       md-on-deselect="announceDeselected(tab)"
 *       ng-disabled="tab.disabled">
 *     <md-tab-label>
 *       {{tab.title}}
 *
 *     </md-tab-label>
 *     <md-tab-body>
 *       {{tab.content}}
 *     </md-tab-body>
 *   </md-tab>
 * </md-tabs>
 * </hljs>
 *
 */
angular
    .module('material.components.tabs')
    .directive('mdTabs', MdTabs);

function MdTabs () {
  return {
    scope:            {
      selectedIndex: '=?mdSelected'
    },
    template:         function (element, attr) {
      attr[ "$mdTabsTemplate" ] = element.html();
      return '' +
        '<md-tabs-wrapper> ' +
          '<md-tab-data></md-tab-data> ' +
          '<md-prev-button ' +
              'tabindex="-1" ' +
              'role="button" ' +
              'aria-label="Previous Page" ' +
              'aria-disabled="{{!$mdTabsCtrl.canPageBack()}}" ' +
              'ng-class="{ \'md-disabled\': !$mdTabsCtrl.canPageBack() }" ' +
              'ng-if="$mdTabsCtrl.shouldPaginate" ' +
              'ng-click="$mdTabsCtrl.previousPage()"> ' +
            '<md-icon md-svg-icon="md-tabs-arrow"></md-icon> ' +
          '</md-prev-button> ' +
          '<md-next-button ' +
              'tabindex="-1" ' +
              'role="button" ' +
              'aria-label="Next Page" ' +
              'aria-disabled="{{!$mdTabsCtrl.canPageForward()}}" ' +
              'ng-class="{ \'md-disabled\': !$mdTabsCtrl.canPageForward() }" ' +
              'ng-if="$mdTabsCtrl.shouldPaginate" ' +
              'ng-click="$mdTabsCtrl.nextPage()"> ' +
            '<md-icon md-svg-icon="md-tabs-arrow"></md-icon> ' +
          '</md-next-button> ' +
          '<md-tabs-canvas ' +
              'tabindex="0" ' +
              'aria-activedescendant="tab-item-{{$mdTabsCtrl.tabs[$mdTabsCtrl.focusIndex].id}}" ' +
              'ng-focus="$mdTabsCtrl.redirectFocus()" ' +
              'ng-class="{ ' +
                  '\'md-paginated\': $mdTabsCtrl.shouldPaginate, ' +
                  '\'md-center-tabs\': $mdTabsCtrl.shouldCenterTabs ' +
              '}" ' +
              'ng-keydown="$mdTabsCtrl.keydown($event)" ' +
              'role="tablist"> ' +
            '<md-pagination-wrapper ' +
                'ng-class="{ \'md-center-tabs\': $mdTabsCtrl.shouldCenterTabs }" ' +
                'md-tab-scroll="$mdTabsCtrl.scroll($event)"> ' +
              '<md-tab-item ' +
                  'tabindex="-1" ' +
                  'class="md-tab" ' +
                  'style="max-width: {{ $mdTabsCtrl.maxTabWidth + \'px\' }}" ' +
                  'ng-repeat="tab in $mdTabsCtrl.tabs" ' +
                  'role="tab" ' +
                  'aria-controls="tab-content-{{::tab.id}}" ' +
                  'aria-selected="{{tab.isActive()}}" ' +
                  'aria-disabled="{{tab.scope.disabled || \'false\'}}" ' +
                  'ng-click="$mdTabsCtrl.select(tab.getIndex())" ' +
                  'ng-class="{ ' +
                      '\'md-active\':    tab.isActive(), ' +
                      '\'md-focused\':   tab.hasFocus(), ' +
                      '\'md-disabled\':  tab.scope.disabled ' +
                  '}" ' +
                  'ng-disabled="tab.scope.disabled" ' +
                  'md-swipe-left="$mdTabsCtrl.nextPage()" ' +
                  'md-swipe-right="$mdTabsCtrl.previousPage()" ' +
                  'md-tabs-template="::tab.label" ' +
                  'md-scope="::tab.parent"></md-tab-item> ' +
              '<md-ink-bar></md-ink-bar> ' +
            '</md-pagination-wrapper> ' +
            '
 ' +
              '<md-dummy-tab ' +
                  'class="md-tab" ' +
                  'tabindex="-1" ' +
                  'id="tab-item-{{::tab.id}}" ' +
                  'role="tab" ' +
                  'aria-controls="tab-content-{{::tab.id}}" ' +
                  'aria-selected="{{tab.isActive()}}" ' +
                  'aria-disabled="{{tab.scope.disabled || \'false\'}}" ' +
                  'ng-focus="$mdTabsCtrl.hasFocus = true" ' +
                  'ng-blur="$mdTabsCtrl.hasFocus = false" ' +
                  'ng-repeat="tab in $mdTabsCtrl.tabs" ' +
                  'md-tabs-template="::tab.label" ' +
                  'md-scope="::tab.parent"></md-dummy-tab> ' +
            '
 ' +
          '</md-tabs-canvas> ' +
        '</md-tabs-wrapper> ' +
        '<md-tabs-content-wrapper ng-show="$mdTabsCtrl.hasContent && $mdTabsCtrl.selectedIndex >= 0"> ' +
          '<md-tab-content ' +
              'id="tab-content-{{::tab.id}}" ' +
              'role="tabpanel" ' +
              'aria-labelledby="tab-item-{{::tab.id}}" ' +
              'md-swipe-left="$mdTabsCtrl.swipeContent && $mdTabsCtrl.incrementIndex(1)" ' +
              'md-swipe-right="$mdTabsCtrl.swipeContent && $mdTabsCtrl.incrementIndex(-1)" ' +
              'ng-if="$mdTabsCtrl.hasContent" ' +
              'ng-repeat="(index, tab) in $mdTabsCtrl.tabs" ' +
              'ng-class="{ ' +
                '\'md-no-transition\': $mdTabsCtrl.lastSelectedIndex == null, ' +
                '\'md-active\':        tab.isActive(), ' +
                '\'md-left\':          tab.isLeft(), ' +
                '\'md-right\':         tab.isRight(), ' +
                '\'md-no-scroll\':     $mdTabsCtrl.dynamicHeight ' +
              '}"> ' +
            '

 ' +
          '</md-tab-content> ' +
        '</md-tabs-content-wrapper>';
    },
    controller:       'MdTabsController',
    controllerAs:     '$mdTabsCtrl',
    bindToController: true
  };
}
<commit_msg>fix(tabs): shift+tab will now work properly when focus is on tabs<commit_after>/**
 * @ngdoc directive
 * @name mdTabs
 * @module material.components.tabs
 *
 * @restrict E
 *
 * @description
 * The `<md-tabs>` directive serves as the container for 1..n `<md-tab>` child directives to produces a Tabs components.
 * In turn, the nested `<md-tab>` directive is used to specify a tab label for the **header button** and a [optional] tab view
 * content that will be associated with each tab button.
 *
 * Below is the markup for its simplest usage:
 *
 *  <hljs lang="html">
 *  <md-tabs>
 *    <md-tab label="Tab #1"></md-tab>
 *    <md-tab label="Tab #2"></md-tab>
 *    <md-tab label="Tab #3"></md-tab>
 *  </md-tabs>
 *  </hljs>
 *
 * Tabs supports three (3) usage scenarios:
 *
 *  1. Tabs (buttons only)
 *  2. Tabs with internal view content
 *  3. Tabs with external view content
 *
 * **Tab-only** support is useful when tab buttons are used for custom navigation regardless of any other components, content, or views.
 * **Tabs with internal views** are the traditional usages where each tab has associated view content and the view switching is managed internally by the Tabs component.
 * **Tabs with external view content** is often useful when content associated with each tab is independently managed and data-binding notifications announce tab selection changes.
 *
 * Additional features also include:
 *
 * *  Content can include any markup.
 * *  If a tab is disabled while active/selected, then the next tab will be auto-selected.
 *
 * ### Explanation of tab stretching
 *
 * Initially, tabs will have an inherent size.  This size will either be defined by how much space is needed to accommodate their text or set by the user through CSS.  Calculations will be based on this size.
 *
 * On mobile devices, tabs will be expanded to fill the available horizontal space.  When this happens, all tabs will become the same size.
 *
 * On desktops, by default, stretching will never occur.
 *
 * This default behavior can be overridden through the `md-stretch-tabs` attribute.  Here is a table showing when stretching will occur:
 *
 * `md-stretch-tabs` | mobile    | desktop
 * ------------------|-----------|--------
 * `auto`            | stretched | ---
 * `always`          | stretched | stretched
 * `never`           | ---       | ---
 *
 * @param {integer=} md-selected Index of the active/selected tab
 * @param {boolean=} md-no-ink If present, disables ink ripple effects.
 * @param {boolean=} md-no-ink-bar If present, disables the selection ink bar.
 * @param {string=}  md-align-tabs Attribute to indicate position of tab buttons: `bottom` or `top`; default is `top`
 * @param {string=} md-stretch-tabs Attribute to indicate whether or not to stretch tabs: `auto`, `always`, or `never`; default is `auto`
 * @param {boolean=} md-dynamic-height When enabled, the tab wrapper will resize based on the contents of the selected tab
 * @param {boolean=} md-center-tabs When enabled, tabs will be centered provided there is no need for pagination
 * @param {boolean=} md-no-pagination When enabled, pagination will remain off
 * @param {boolean=} md-swipe-content When enabled, swipe gestures will be enabled for the content area to jump between tabs
 * @param {boolean=} md-enable-disconnect When enabled, scopes will be disconnected for tabs that are not being displayed.  This provides a performance boost, but may also cause unexpected issues and is not recommended for most users.
 * @param {boolean=} md-autoselect When present, any tabs added after the initial load will be automatically selected
 *
 * @usage
 * <hljs lang="html">
 * <md-tabs md-selected="selectedIndex" >
 *
 *   <md-tab
 *       ng-repeat="tab in tabs | orderBy:predicate:reversed"
 *       md-on-select="onTabSelected(tab)"
 *       md-on-deselect="announceDeselected(tab)"
 *       ng-disabled="tab.disabled">
 *     <md-tab-label>
 *       {{tab.title}}
 *
 *     </md-tab-label>
 *     <md-tab-body>
 *       {{tab.content}}
 *     </md-tab-body>
 *   </md-tab>
 * </md-tabs>
 * </hljs>
 *
 */
angular
    .module('material.components.tabs')
    .directive('mdTabs', MdTabs);

function MdTabs () {
  return {
    scope:            {
      selectedIndex: '=?mdSelected'
    },
    template:         function (element, attr) {
      attr[ "$mdTabsTemplate" ] = element.html();
      return '' +
        '<md-tabs-wrapper> ' +
          '<md-tab-data></md-tab-data> ' +
          '<md-prev-button ' +
              'tabindex="-1" ' +
              'role="button" ' +
              'aria-label="Previous Page" ' +
              'aria-disabled="{{!$mdTabsCtrl.canPageBack()}}" ' +
              'ng-class="{ \'md-disabled\': !$mdTabsCtrl.canPageBack() }" ' +
              'ng-if="$mdTabsCtrl.shouldPaginate" ' +
              'ng-click="$mdTabsCtrl.previousPage()"> ' +
            '<md-icon md-svg-icon="md-tabs-arrow"></md-icon> ' +
          '</md-prev-button> ' +
          '<md-next-button ' +
              'tabindex="-1" ' +
              'role="button" ' +
              'aria-label="Next Page" ' +
              'aria-disabled="{{!$mdTabsCtrl.canPageForward()}}" ' +
              'ng-class="{ \'md-disabled\': !$mdTabsCtrl.canPageForward() }" ' +
              'ng-if="$mdTabsCtrl.shouldPaginate" ' +
              'ng-click="$mdTabsCtrl.nextPage()"> ' +
            '<md-icon md-svg-icon="md-tabs-arrow"></md-icon> ' +
          '</md-next-button> ' +
          '<md-tabs-canvas ' +
              'tabindex="{{ $mdTabsCtrl.hasFocus ? -1 : 0 }}" ' +
              'aria-activedescendant="tab-item-{{$mdTabsCtrl.tabs[$mdTabsCtrl.focusIndex].id}}" ' +
              'ng-focus="$mdTabsCtrl.redirectFocus()" ' +
              'ng-class="{ ' +
                  '\'md-paginated\': $mdTabsCtrl.shouldPaginate, ' +
                  '\'md-center-tabs\': $mdTabsCtrl.shouldCenterTabs ' +
              '}" ' +
              'ng-keydown="$mdTabsCtrl.keydown($event)" ' +
              'role="tablist"> ' +
            '<md-pagination-wrapper ' +
                'ng-class="{ \'md-center-tabs\': $mdTabsCtrl.shouldCenterTabs }" ' +
                'md-tab-scroll="$mdTabsCtrl.scroll($event)"> ' +
              '<md-tab-item ' +
                  'tabindex="-1" ' +
                  'class="md-tab" ' +
                  'style="max-width: {{ $mdTabsCtrl.maxTabWidth + \'px\' }}" ' +
                  'ng-repeat="tab in $mdTabsCtrl.tabs" ' +
                  'role="tab" ' +
                  'aria-controls="tab-content-{{::tab.id}}" ' +
                  'aria-selected="{{tab.isActive()}}" ' +
                  'aria-disabled="{{tab.scope.disabled || \'false\'}}" ' +
                  'ng-click="$mdTabsCtrl.select(tab.getIndex())" ' +
                  'ng-class="{ ' +
                      '\'md-active\':    tab.isActive(), ' +
                      '\'md-focused\':   tab.hasFocus(), ' +
                      '\'md-disabled\':  tab.scope.disabled ' +
                  '}" ' +
                  'ng-disabled="tab.scope.disabled" ' +
                  'md-swipe-left="$mdTabsCtrl.nextPage()" ' +
                  'md-swipe-right="$mdTabsCtrl.previousPage()" ' +
                  'md-tabs-template="::tab.label" ' +
                  'md-scope="::tab.parent"></md-tab-item> ' +
              '<md-ink-bar></md-ink-bar> ' +
            '</md-pagination-wrapper> ' +
            '
 ' +
              '<md-dummy-tab ' +
                  'class="md-tab" ' +
                  'tabindex="-1" ' +
                  'id="tab-item-{{::tab.id}}" ' +
                  'role="tab" ' +
                  'aria-controls="tab-content-{{::tab.id}}" ' +
                  'aria-selected="{{tab.isActive()}}" ' +
                  'aria-disabled="{{tab.scope.disabled || \'false\'}}" ' +
                  'ng-focus="$mdTabsCtrl.hasFocus = true" ' +
                  'ng-blur="$mdTabsCtrl.hasFocus = false" ' +
                  'ng-repeat="tab in $mdTabsCtrl.tabs" ' +
                  'md-tabs-template="::tab.label" ' +
                  'md-scope="::tab.parent"></md-dummy-tab> ' +
            '
 ' +
          '</md-tabs-canvas> ' +
        '</md-tabs-wrapper> ' +
        '<md-tabs-content-wrapper ng-show="$mdTabsCtrl.hasContent && $mdTabsCtrl.selectedIndex >= 0"> ' +
          '<md-tab-content ' +
              'id="tab-content-{{::tab.id}}" ' +
              'role="tabpanel" ' +
              'aria-labelledby="tab-item-{{::tab.id}}" ' +
              'md-swipe-left="$mdTabsCtrl.swipeContent && $mdTabsCtrl.incrementIndex(1)" ' +
              'md-swipe-right="$mdTabsCtrl.swipeContent && $mdTabsCtrl.incrementIndex(-1)" ' +
              'ng-if="$mdTabsCtrl.hasContent" ' +
              'ng-repeat="(index, tab) in $mdTabsCtrl.tabs" ' +
              'ng-class="{ ' +
                '\'md-no-transition\': $mdTabsCtrl.lastSelectedIndex == null, ' +
                '\'md-active\':        tab.isActive(), ' +
                '\'md-left\':          tab.isLeft(), ' +
                '\'md-right\':         tab.isRight(), ' +
                '\'md-no-scroll\':     $mdTabsCtrl.dynamicHeight ' +
              '}"> ' +
            '

 ' +
          '</md-tab-content> ' +
        '</md-tabs-content-wrapper>';
    },
    controller:       'MdTabsController',
    controllerAs:     '$mdTabsCtrl',
    bindToController: true
  };
}
<|endoftext|>


<commit_before>import React from 'react';
import {connect} from 'react-redux';
import {ReverseLink} from 'react-router-reverse';
import {bindActionCreators} from 'redux';

import {fxaLoginBegin, login} from '../actions/login';
import {LoginButton} from '../components/login';
import {Page} from '../components/page';

export class Landing extends React.Component {
  static propTypes = {
    fxaLoginBegin: React.PropTypes.func.isRequired,
    login: React.PropTypes.func.isRequired,
    user: React.PropTypes.object,
    signTOS: React.PropTypes.func.isRequired,
    siteConfig: React.PropTypes.object.isRequired,
  };

  loginHandler = authCode => {
    // Call login, passing in some extra stuff from siteConfig.
    this.props.login(authCode, this.props.siteConfig.authState,
                     this.props.siteConfig.clientId);
  }

  render() {
    const isLoggedOut = !this.props.user || !this.props.user.token;
    return (
      <Page className="landing">

New for Firefox OS… Add-ons.

Just like for the browser. Only for mobile.

              Learn how to make a Firefox OS add-on ›

Submit a new add-on or manage your current add-ons:

            <ReverseLink className="button" to="addon-submit">
              Submit a Firefox OS Add-On ›
            </ReverseLink>

          {isLoggedOut &&

Don't have an account?

              <LoginButton authUrl={this.props.siteConfig.authUrl}
                           content="Create One Now ›"
                           loginBeginHandler={this.props.fxaLoginBegin}
                           loginHandler={this.loginHandler}
                           isSignup={true}/>

          }

Join the Community

Blog

Discussion forum

Mailing list

Learn more

      </Page>
    );
  }
}

export default connect(
  state => ({
    siteConfig: state.siteConfig,
    user: state.user,
  }),
  dispatch => bindActionCreators({
    fxaLoginBegin,
    login
  }, dispatch)
)(Landing);
<commit_msg>change landing page copy (bug 1210207)<commit_after>import React from 'react';
import {connect} from 'react-redux';
import {ReverseLink} from 'react-router-reverse';
import {bindActionCreators} from 'redux';

import {fxaLoginBegin, login} from '../actions/login';
import {LoginButton} from '../components/login';
import {Page} from '../components/page';

export class Landing extends React.Component {
  static propTypes = {
    fxaLoginBegin: React.PropTypes.func.isRequired,
    login: React.PropTypes.func.isRequired,
    user: React.PropTypes.object,
    signTOS: React.PropTypes.func.isRequired,
    siteConfig: React.PropTypes.object.isRequired,
  };

  loginHandler = authCode => {
    // Call login, passing in some extra stuff from siteConfig.
    this.props.login(authCode, this.props.siteConfig.authState,
                     this.props.siteConfig.clientId);
  }

  render() {
    const isLoggedOut = !this.props.user || !this.props.user.token;
    return (
      <Page className="landing">

Personalize your Firefox OS experience with powerful add-ons

              Learn how to make a Firefox OS add-on ›

Submit a new add-on or manage your current add-ons:

            <ReverseLink className="button" to="addon-submit">
              Submit a Firefox OS Add-On ›
            </ReverseLink>

          {isLoggedOut &&

Don't have an account?

              <LoginButton authUrl={this.props.siteConfig.authUrl}
                           content="Create One Now ›"
                           loginBeginHandler={this.props.fxaLoginBegin}
                           loginHandler={this.loginHandler}
                           isSignup={true}/>

          }

Join the Community

Blog

Discussion forum

Mailing list

Learn more

      </Page>
    );
  }
}

export default connect(
  state => ({
    siteConfig: state.siteConfig,
    user: state.user,
  }),
  dispatch => bindActionCreators({
    fxaLoginBegin,
    login
  }, dispatch)
)(Landing);
<|endoftext|>


<commit_before>import Tip from '../'

it('test Tip default porps', () => {
  const vm = shallow({
    render(h) {
      return (
        <Tip>
          tip
          <Tip.Context>
            tipContext
          </Tip.Context>
        </Tip>
      )
    }
  })
  expect(vm.pos).toBe('down')
  expect(vm.event).toBe('mouseenter')
})

it('test mouse event', async() => {
  const vm = shallow({
    render(h) {
      return (
        <Tip>
          tip
          <Tip.Context>
            tipContext
          </Tip.Context>
        </Tip>
      )
    }
  })

  //when mouse enters
  const eventEnter = document.createEvent('HTMLEvents')
  eventEnter.initEvent('mouseenter', true, false)
  vm.$el.dispatchEvent(eventEnter)
  //tipContext should show up
  const tipContext2 = document.querySelector('#tipContext2')
  expect(getComputedStyle(tipContext2)._values.display).toBe('block')
  await new Promise((done) => {
    setTimeout(() => {
      done()
    }, 50)
  })
  expect(getComputedStyle(tipContext2)._values.opacity).toBe('1')
  //when mouse leaves
  const eventLeave = document.createEvent('HTMLEvents')
  eventLeave.initEvent('mouseleave', true, false)
  vm.$el.dispatchEvent(eventLeave)
  //tipContext should disappear
  expect(document.querySelector('#tipContext2')).toBe(null)
})

it('test Tip props set `pos`=right', () => {
  const vm = shallow({
    render(h) {
      return (
        <Tip pos="right">
          tip
          <Tip.Context>
            tipContext
          </Tip.Context>
        </Tip>
      )
    }
  })
  expect(vm.pos).toBe('right')

  const eventEnter = document.createEvent('HTMLEvents')
  eventEnter.initEvent('mouseenter', true, false)
  vm.$el.dispatchEvent(eventEnter)
})

it('test Tip props set `pos`=left', () => {
  const vm = shallow({
    render(h) {
      return (
        <Tip pos="left">
          tip
          <Tip.Context>
            tipContext
          </Tip.Context>
        </Tip>
      )
    }
  })
  expect(vm.pos).toBe('left')

  const eventEnter = document.createEvent('HTMLEvents')
  eventEnter.initEvent('mouseenter', true, false)
  vm.$el.dispatchEvent(eventEnter)
})

it('test Tip props set `pos`=up', () => {
  const vm = shallow({
    render(h) {
      return (
        <Tip pos="up">
          tip
          <Tip.Context>
            tipContext
          </Tip.Context>
        </Tip>
      )
    }
  })
  expect(vm.pos).toBe('up')

  const eventEnter = document.createEvent('HTMLEvents')
  eventEnter.initEvent('mouseenter', true, false)
  vm.$el.dispatchEvent(eventEnter)
})

it('test Tip props set `pos`=down', () => {
  const vm = shallow({
    render(h) {
      return (
        <Tip pos="down">
          tip
          <Tip.Context>
            tipContext
          </Tip.Context>
        </Tip>
      )
    }
  })
  expect(vm.pos).toBe('down')

  const eventEnter = document.createEvent('HTMLEvents')
  eventEnter.initEvent('mouseenter', true, false)
  vm.$el.dispatchEvent(eventEnter)
})
<commit_msg>[OWL-1082] trigger mouse event using cash-dom<commit_after>import Tip from '../'

it('test Tip default porps', () => {
  const vm = shallow({
    render(h) {
      return (
        <Tip>
          tip
          <Tip.Context>
            tipContext
          </Tip.Context>
        </Tip>
      )
    }
  })
  expect(vm.pos).toBe('down')
  expect(vm.event).toBe('mouseenter')
})

it('test mouse event', async() => {
  const vm = shallow({
    render(h) {
      return (
        <Tip>
          tip
          <Tip.Context>
            tipContext
          </Tip.Context>
        </Tip>
      )
    }
  })

  //when mouse enters
  $(vm.$el).trigger('mouseenter')
  //tipContext should show up
  const tipContext2 = document.querySelector('#tipContext2')
  expect(getComputedStyle(tipContext2)._values.display).toBe('block')
  await new Promise((done) => {
    setTimeout(() => {
      done()
    }, 50)
  })
  expect(getComputedStyle(tipContext2)._values.opacity).toBe('1')
  //when mouse leaves
  $(vm.$el).trigger('mouseleave')
  //tipContext should disappear
  expect(document.querySelector('#tipContext2')).toBe(null)
})

it('test Tip props set `pos`=right', () => {
  const vm = shallow({
    render(h) {
      return (
        <Tip pos="right">
          tip
          <Tip.Context>
            tipContext
          </Tip.Context>
        </Tip>
      )
    }
  })
  expect(vm.pos).toBe('right')

  $(vm.$el).trigger('mouseenter')
})

it('test Tip props set `pos`=left', () => {
  const vm = shallow({
    render(h) {
      return (
        <Tip pos="left">
          tip
          <Tip.Context>
            tipContext
          </Tip.Context>
        </Tip>
      )
    }
  })
  expect(vm.pos).toBe('left')

  $(vm.$el).trigger('mouseenter')
})

it('test Tip props set `pos`=up', () => {
  const vm = shallow({
    render(h) {
      return (
        <Tip pos="up">
          tip
          <Tip.Context>
            tipContext
          </Tip.Context>
        </Tip>
      )
    }
  })
  expect(vm.pos).toBe('up')

  $(vm.$el).trigger('mouseenter')
})

it('test Tip props set `pos`=down', () => {
  const vm = shallow({
    render(h) {
      return (
        <Tip pos="down">
          tip
          <Tip.Context>
            tipContext
          </Tip.Context>
        </Tip>
      )
    }
  })
  expect(vm.pos).toBe('down')

  $(vm.$el).trigger('mouseenter')
})
<|endoftext|>


<commit_before>/*
**  BindingJS -- View Data Binding for JavaScript <http://bindingjs.com>
**  Copyright (c) 2014 Johannes Rummel
**
**  This Source Code Form is subject to the terms of the Mozilla Public
**  License (MPL), version 2.0. If a copy of the MPL was not distributed
**  with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

/* global WatchJS */
/* global JSON */

/**
 * DEVELOPED BY
 * GIL LOPES BUENO
 * gilbueno.mail@gmail.com
 *
 * WORKS WITH:
 * IE 9+, FF 4+, SF 5+, WebKit, CH 7+, OP 12+, BESEN, Rhino 1.7+
 *
 * FORK:
 * https://github.com/melanke/Watch.JS
 */
// jshint ignore: start
"use strict";
(function (factory) {
    if (typeof exports === 'object') {
        // Node. Does not work with strict CommonJS, but
        // only CommonJS-like enviroments that support module.exports,
        // like Node.
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(factory);
    } else {
        // Browser globals
        window.WatchJS = factory();
        window.watch = window.WatchJS.watch;
        window.unwatch = window.WatchJS.unwatch;
        window.callWatchers = window.WatchJS.callWatchers;
    }
}(function () {

    var WatchJS = {
        noMore: false
    },
    lengthsubjects = [];

    var isFunction = function (functionToCheck) {
            var getType = {};
            return functionToCheck && getType.toString.call(functionToCheck) == '[object Function]';
    };

    var isInt = function (x) {
        return x % 1 === 0;
    };

    var isArray = function(obj) {
        return Object.prototype.toString.call(obj) === '[object Array]';
    };

    var getObjDiff = function(a, b){
        var aplus = [],
        bplus = [];

        if(!(typeof a == "string") && !(typeof b == "string")){

            if (isArray(a)) {
                for (var i=0; i<a.length; i++) {
                    if (b[i] === undefined) aplus.push(i);
                }
            } else {
                for(var i in a){
                    if (a.hasOwnProperty(i)) {
                        if(b[i] === undefined) {
                            aplus.push(i);
                        }
                    }
                }
            }

            if (isArray(b)) {
                for (var j=0; j<b.length; j++) {
                    if (a[j] === undefined) bplus.push(j);
                }
            } else {
                for(var j in b){
                    if (b.hasOwnProperty(j)) {
                        if(a[j] === undefined) {
                            bplus.push(j);
                        }
                    }
                }
            }
        }

        return {
            added: aplus,
            removed: bplus
        }
    };

    var clone = function(obj){

        if (null == obj || "object" != typeof obj) {
            return obj;
        }

        var copy = obj.constructor();

        for (var attr in obj) {
            copy[attr] = obj[attr];
        }

        return copy;

    }

    var defineGetAndSet = function (obj, propName, getter, setter) {
        try {

            Object.observe(obj, function(changes) {
                // Object.observe catches errors, this is bad, nothing we can do
                try {
                    changes.forEach(function(change) {
                        if (change.name === propName) {
                            setter(change.object[change.name]);
                        }
                    });
                 } catch (e) {
                    console.log("An error inside Object.observe occured, since it does catch, this is the error:")
                    console.log(e)
                    throw e
                 }
            });

        } catch(e) {

            try {
                    Object.defineProperty(obj, propName, {
                            get: getter,
                            set: setter,
                            enumerable: true,
                            configurable: true
                    });
            } catch(e2) {
                try{
                    Object.prototype.__defineGetter__.call(obj, propName, getter);
                    Object.prototype.__defineSetter__.call(obj, propName, setter);
                } catch(e3) {
                    throw new Error("watchJS error: browser not supported :/")
                }
            }

        }
    };

    var defineProp = function (obj, propName, value) {
        try {
            Object.defineProperty(obj, propName, {
                enumerable: false,
                configurable: true,
                writable: false,
                value: value
            });
        } catch(error) {
            obj[propName] = value;
        }
    };

    var watch = function () {

        if (isFunction(arguments[1])) {
            watchAll.apply(this, arguments);
        } else if (isArray(arguments[1])) {
            watchMany.apply(this, arguments);
        } else {
            watchOne.apply(this, arguments);
        }

    };

    var watchAll = function (obj, watcher, level, addNRemove) {

        if ((typeof obj == "string") || (!(obj instanceof Object) && !isArray(obj))) { //accepts only objects and array (not string)
            return;
        }

        var props = [];

        if(isArray(obj)) {
            for (var prop = 0; prop < obj.length; prop++) { //for each item if obj is an array
                props.push(prop); //put in the props
            }
        } else {
            for (var prop2 in obj) { //for each attribute if obj is an object
				if (prop2 == "$val") {
					continue;
				}

                if (Object.prototype.hasOwnProperty.call(obj, prop2)) {
                    props.push(prop2); //put in the props
                }
            }
        }

        watchMany(obj, props, watcher, level, addNRemove); //watch all items of the props

        if (addNRemove) {
            pushToLengthSubjects(obj, "$$watchlengthsubjectroot", watcher, level);
        }
    };

    var watchMany = function (obj, props, watcher, level, addNRemove) {

        if ((typeof obj == "string") || (!(obj instanceof Object) && !isArray(obj))) { //accepts only objects and array (not string)
            return;
        }

        for (var i=0; i<props.length; i++) { //watch each property
            var prop = props[i];
            watchOne(obj, prop, watcher, level, addNRemove);
        }

    };

    var watchOne = function (obj, prop, watcher, level, addNRemove) {
        if ((typeof obj == "string") || (!(obj instanceof Object) && !isArray(obj))) { //accepts only objects and array (not string)
            return;
        }

        if(isFunction(obj[prop])) { //dont watch if it is a function
            return;
        }
        if(obj[prop] != null && (level === undefined || level > 0)){
            watchAll(obj[prop], watcher, level!==undefined? level-1 : level); //recursively watch all attributes of this
        }

        defineWatcher(obj, prop, watcher, level);

        if(addNRemove && (level === undefined || level > 0)){
            pushToLengthSubjects(obj, prop, watcher, level);
        }

    };

    var unwatch = function () {

        if (isFunction(arguments[1])) {
            unwatchAll.apply(this, arguments);
        } else if (isArray(arguments[1])) {
            unwatchMany.apply(this, arguments);
        } else {
            unwatchOne.apply(this, arguments);
        }

    };

    var unwatchAll = function (obj, watcher) {

        if (obj instanceof String || (!(obj instanceof Object) && !isArray(obj))) { //accepts only objects and array (not string)
            return;
        }

        if (isArray(obj)) {
            var props = [];
            for (var prop = 0; prop < obj.length; prop++) { //for each item if obj is an array
                props.push(prop); //put in the props
            }
            unwatchMany(obj, props, watcher); //watch all itens of the props
        } else {
            var unwatchPropsInObject = function (obj2) {
                var props = [];
                for (var prop2 in obj2) { //for each attribute if obj is an object
                    if (obj2.hasOwnProperty(prop2)) {
                        if (obj2[prop2] instanceof Object) {
                            unwatchPropsInObject(obj2[prop2]); //recurs into object props
                        } else {
                            props.push(prop2); //put in the props
                        }
                    }
                }
                unwatchMany(obj2, props, watcher); //unwatch all of the props
            };
            unwatchPropsInObject(obj);
        }
    };

    var unwatchMany = function (obj, props, watcher) {

        for (var prop2 in props) { //watch each attribute of "props" if is an object
            if (props.hasOwnProperty(prop2)) {
                unwatchOne(obj, props[prop2], watcher);
            }
        }
    };

    var defineWatcher = function (obj, prop, watcher, level) {

        var val = obj[prop];

        watchFunctions(obj, prop);

        if (!obj.watchers) {
            defineProp(obj, "watchers", {});
        }

        if (!obj.watchers[prop]) {
            obj.watchers[prop] = [];
        }

        for (var i=0; i<obj.watchers[prop].length; i++) {
            if(obj.watchers[prop][i] === watcher){
                return;
            }
        }

        obj.watchers[prop].push(watcher); //add the new watcher in the watchers array

        var getter = function () {
            return val;
        };

        var setter = function (newval) {
            var oldval = val;
            val = newval;

            if (level !== 0 && obj[prop]){
                // watch sub properties
                watchAll(obj[prop], watcher, (level===undefined)?level:level-1);
            }

            watchFunctions(obj, prop);

            if (!WatchJS.noMore){
                //if (JSON.stringify(oldval) !== JSON.stringify(newval)) {
                if (oldval !== newval) {
                    callWatchers(obj, prop, "set", newval, oldval);
                    WatchJS.noMore = false;
                }
            }
        };

        defineGetAndSet(obj, prop, getter, setter);

    };

    var callWatchers = function (obj, prop, action, newval, oldval) {
        if (prop !== undefined) {
            for (var wr=0; wr<obj.watchers[prop].length; wr++) {
                obj.watchers[prop][wr].call(obj, prop, action, newval, oldval);
            }
        } else {
            for (var prop in obj) {//call all
                if (obj.hasOwnProperty(prop)) {
                    callWatchers(obj, prop, action, newval, oldval);
                }
            }
        }
    };

    // @todo code related to "watchFunctions" is certainly buggy
    var methodNames = ['pop', 'push', 'reverse', 'shift', 'sort', 'slice', 'unshift', 'splice'];
    var defineArrayMethodWatcher = function (obj, prop, original, methodName) {
        defineProp(obj[prop], methodName, function () {
            var response = original.apply(obj[prop], arguments);
            watchOne(obj, obj[prop]);
            if (methodName !== 'slice') {
                callWatchers(obj, prop, methodName,arguments);
            }
            return response;
        });
    };

    var watchFunctions = function(obj, prop) {

        if ((!obj[prop]) || (obj[prop] instanceof String) || (!isArray(obj[prop]))) {
            return;
        }

        for (var i = methodNames.length, methodName; i--;) {
            methodName = methodNames[i];
            defineArrayMethodWatcher(obj, prop, obj[prop][methodName], methodName);
        }

    };

    var unwatchOne = function (obj, prop, watcher) {
        for (var i=0; i<obj.watchers[prop].length; i++) {
            var w = obj.watchers[prop][i];

            if(w == watcher) {
                obj.watchers[prop].splice(i, 1);
            }
        }

        removeFromLengthSubjects(obj, prop, watcher);
    };

    var loop = function(){

        for(var i=0; i<lengthsubjects.length; i++) {

            var subj = lengthsubjects[i];

            if (subj.prop === "$$watchlengthsubjectroot") {

                var difference = getObjDiff(subj.obj, subj.actual);

                if(difference.added.length || difference.removed.length){
                    if(difference.added.length){
                        watchMany(subj.obj, difference.added, subj.watcher, subj.level - 1, true);
                    }

                    subj.watcher.call(subj.obj, "root", "differentattr", difference, subj.actual);
                }
                subj.actual = clone(subj.obj);

            } else {

                var difference = getObjDiff(subj.obj[subj.prop], subj.actual);

                if(difference.added.length || difference.removed.length){
                    if(difference.added.length){
                        for (var j=0; j<subj.obj.watchers[subj.prop].length; j++) {
                            watchMany(subj.obj[subj.prop], difference.added, subj.obj.watchers[subj.prop][j], subj.level - 1, true);
                        }
                    }

                    callWatchers(subj.obj, subj.prop, "differentattr", difference, subj.actual);
                }

                subj.actual = clone(subj.obj[subj.prop]);

            }

        }

    };

    var pushToLengthSubjects = function(obj, prop, watcher, level){

        var actual;

        if (prop === "$$watchlengthsubjectroot") {
            actual =  clone(obj);
        } else {
            actual = clone(obj[prop]);
        }

        lengthsubjects.push({
            obj: obj,
            prop: prop,
            actual: actual,
            watcher: watcher,
            level: level
        });
    };

    var removeFromLengthSubjects = function(obj, prop, watcher){

        for (var i=0; i<lengthsubjects.length; i++) {
            var subj = lengthsubjects[i];

            if (subj.obj == obj && subj.prop == prop && subj.watcher == watcher) {
                lengthsubjects.splice(i, 1);
            }
        }

    };

    setInterval(loop, 50);

    WatchJS.watch = watch;
    WatchJS.unwatch = unwatch;
    WatchJS.callWatchers = callWatchers;

    return WatchJS;

}));
// jshint ignore: end

BindingJS.plugin("$", ($api, _api) => {
    class JsonAdapter {

        constructor () {
            this.observer = new _api.util.Map()
            this.counter = new _api.util.Counter()
        }

        /* private */ notify (model, path) {
            if (this.observer.hasKey(model)) {
                _api.util.each(this.observer.get(model), (observer) => {
                    if (_api.util.object.equals(path, observer.path)) {
                        observer.callback()
                    }
                })
            }
        }

        observe (model, path, callback) {
            if (path.length === 0) {
                throw _api.util.exception("$ Adapter cannot be used without a Qualifier")
            }

            if (!this.observer.hasKey(model)) {
                this.observer.set(model, [])
            }

            let id = this.counter.getNext()
            let newObserver = { id: id, path: path, callback: callback }
            this.observer.get(model).push(newObserver)

            let observerWithSamePath = _api.util.array.findFirst(this.observer.get(model), (observer) => {
                return observer !== newObserver && _api.util.object.equals(path, observer.path)
            })
            if (!_api.util.object.isDefined(observerWithSamePath)) {
                // Navigate to parent of element, that should be observed
                let elem = model
                for (let i = 0; i < path.length - 1; i++) {
                    elem = elem[path[i]]
                }
                let self = this
                WatchJS.watch(elem,
                              path[path.length - 1] + "" /* WatchJS has trouble if the attribute name is not a string */,
                              () => { self.notify(model, path) })
            }

            return id
        }

        unobserve (id) {
            _api.util.each(this.observer.getKeys(), (model, _, __, breakOuter) => {
                let found = false
                _api.util.each(this.observer.get(model), (observer, ___, ____, breaK) => {
                    if (observer.id === id) {
                        _api.util.array.remove(this.observer.get(model), observer)
                        if (this.observer.get(model).length === 0) {
                            this.observer.remove(model)
                        }
                        found = true
                        return breaK
                    }
                })
                if (found) {
                    return breakOuter
                }
            })
        }

        getValue (model, path, params) {
            let ref = model
            _api.util.each(path, (id) => {
                if (!ref.hasOwnProperty(id)) {
                    throw _api.util.exception("Could not find path " +
                        JSON.stringify(path) + " in Presentation Model")
                }
                ref = ref[id]
            })

            if (ref instanceof Function) {
                return ref(params)
            } else if (ref instanceof Array) {
                return _api.util.array.clone(ref)
            } else if (typeof ref === "object") {
                return _api.util.object.clone(ref)
            } else {
                return ref
            }
        }

        getPaths (model, path) {
            let paths = [path]
            let value = this.getValue(model, path)
            if (value instanceof Array || typeof value === "object") {
                _api.util.each(value, (_, indexOrKey) => {
                    let newPath = _api.util.array.clone(path)
                    newPath.push(indexOrKey)
                    // Recursion
                    _api.util.array.addAll(paths, this.getPaths(model, newPath))
                })
            }
            return paths
        }

        set (model, path, value, params) {
            if (path.length === 0) {
                throw _api.util.exception("$ Adapter cannot be used without a Qualifier")
            }

            let ref = model
            _api.util.each(path, (id, index) => {
                if (index < path.length - 1) {
                    if (!ref.hasOwnProperty(id)) {
                        throw _api.util.exception("Could not find path " +
                            JSON.stringify(path) + " in Presentation Model")
                    }
                    ref = ref[id]
                }
            })

            let target = ref[path[path.length - 1]]
            if (target instanceof Function) {
                target(value, params)
            } else {
                ref[path[path.length - 1]] = value
            }
        }

        type () {
            return "model"
        }
    }

    return new JsonAdapter()
})
<commit_msg>Throwing out Watch.JS, implemented own poll mechanism<commit_after>/*
**  BindingJS -- View Data Binding for JavaScript <http://bindingjs.com>
**  Copyright (c) 2014 Johannes Rummel
**
**  This Source Code Form is subject to the terms of the Mozilla Public
**  License (MPL), version 2.0. If a copy of the MPL was not distributed
**  with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

/* global JSON */
/* global setInterval */

BindingJS.plugin("$", ($api, _api) => {
    class JsonAdapter {

        constructor () {
            this.observer = new _api.util.Map()
            this.oldModels = new _api.util.Map()
            this.counter = new _api.util.Counter()
        }

        /* private */ compare (model, oldModel, pathSoFar) {
            let result = []
            if (model instanceof Array) {
                if (!(oldModel instanceof Array) || model.length !== oldModel.length) {
                    result.push(pathSoFar)
                } else {
                    _api.util.each(model, (item, index) => {
                        let newPath = _api.util.array.clone(pathSoFar)
                        newPath.push(index)
                        _api.util.array.addAll(result, this.compare(model[index],
                                                                    oldModel[index],
                                                                    newPath))
                    })
                }
            } else if (typeof model === "object") {
                if (typeof oldModel !== "object" ||
                    !_api.util.object.equals(_api.util.object.getKeys(model),
                                             _api.util.object.getKeys(oldModel))) {
                    result.push(pathSoFar)
                } else {
                    _api.util.each(model, (item, key) => {
                        let newPath = _api.util.array.clone(pathSoFar)
                        newPath.push(key)
                        _api.util.array.addAll(result, this.compare(model[key],
                                                                    oldModel[key],
                                                                    newPath))
                    })
                }
            } else {
                if (model !== oldModel) {
                    result.push(pathSoFar)
                }
            }
            return result
        }

        /* private */ notify (model) {
            let oldModel = this.oldModels.get(model)
            let pathsChanged = this.compare(model, oldModel, [])
            if (this.observer.hasKey(model)) {
                let modelObservers = this.observer.get(model)
                _api.util.each(pathsChanged, (pathChanged) => {
                    _api.util.each(modelObservers, (modelObserver) => {
                        if (_api.util.array.startsWith(pathChanged, modelObserver.path)) {
                            modelObserver.callback()
                        }
                    })
                })
            }
            this.oldModels.set(model, _api.util.object.clone(model))
        }

        observe (model, path, params, callback) {
            if (path.length === 0) {
                throw _api.util.exception("$ Adapter cannot be used without a Qualifier")
            }

            if (!this.observer.hasKey(model)) {
                this.observer.set(model, [])
                this.oldModels.set(model, _api.util.object.clone(model))
                let self = this
                setInterval(() => { self.notify(model) }, 50)
            }

            let id = this.counter.getNext()
            let newObserver = { id: id, path: path, callback: callback }
            this.observer.get(model).push(newObserver)

            return id
        }

        unobserve (id) {
            _api.util.each(this.observer.getKeys(), (model, _, __, breakOuter) => {
                let found = false
                _api.util.each(this.observer.get(model), (observer, ___, ____, breaK) => {
                    if (observer.id === id) {
                        _api.util.array.remove(this.observer.get(model), observer)
                        if (this.observer.get(model).length === 0) {
                            this.observer.remove(model)
                        }
                        found = true
                        return breaK
                    }
                })
                if (found) {
                    return breakOuter
                }
            })
        }

        getValue (model, path, params) {
            let ref = model
            _api.util.each(path, (id) => {
                if (!ref.hasOwnProperty(id)) {
                    throw _api.util.exception("Could not find path " +
                        JSON.stringify(path) + " in Presentation Model")
                }
                ref = ref[id]
            })

            if (ref instanceof Function) {
                return ref(params)
            } else if (ref instanceof Array) {
                return _api.util.array.clone(ref)
            } else if (typeof ref === "object") {
                return _api.util.object.clone(ref)
            } else {
                return ref
            }
        }

        getPaths (model, path) {
            let paths = [path]
            let value = this.getValue(model, path)
            if (value instanceof Array || typeof value === "object") {
                _api.util.each(value, (_, indexOrKey) => {
                    let newPath = _api.util.array.clone(path)
                    newPath.push(indexOrKey)
                    // Recursion
                    _api.util.array.addAll(paths, this.getPaths(model, newPath))
                })
            }
            return paths
        }

        set (model, path, value, params) {
            if (path.length === 0) {
                throw _api.util.exception("$ Adapter cannot be used without a Qualifier")
            }

            let ref = model
            _api.util.each(path, (id, index) => {
                if (index < path.length - 1) {
                    if (!ref.hasOwnProperty(id)) {
                        throw _api.util.exception("Could not find path " +
                            JSON.stringify(path) + " in Presentation Model")
                    }
                    ref = ref[id]
                }
            })

            let target = ref[path[path.length - 1]]
            if (target instanceof Function) {
                target(value, params)
            } else {
                ref[path[path.length - 1]] = value
            }
        }

        type () {
            return "model"
        }
    }

    return new JsonAdapter()
})
<|endoftext|>


<commit_before>/*
Copyright 2015, 2016 OpenMarket Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

'use strict';

const React = require('react');
const MatrixClientPeg = require('../../../MatrixClientPeg');
const sdk = require('../../../index');
const classNames = require('classnames');
const AccessibleButton = require('../../../components/views/elements/AccessibleButton');
import { _t } from '../../../languageHandler';

module.exports = React.createClass({
    displayName: 'SearchBar',

    getInitialState: function() {
        return ({
            scope: 'Room',
        });
    },

    onThisRoomClick: function() {
        this.setState({ scope: 'Room' }, () => this._searchIfQuery());
    },

    onAllRoomsClick: function() {
        this.setState({ scope: 'All' }, () => this._searchIfQuery());
    },

    onSearchChange: function(e) {
        if (e.keyCode === 13) { // on enter...
            this.onSearch();
        }
        if (e.keyCode === 27) { // escape...
            this.props.onCancelClick();
        }
    },

    _searchIfQuery: function() {
        if (this.refs.search_term.value) {
            this.onSearch();
        }
    },

    onSearch: function() {
        this.props.onSearch(this.refs.search_term.value, this.state.scope);
    },

    render: function() {
        const searchButtonClasses = classNames({ mx_SearchBar_searchButton: true, mx_SearchBar_searching: this.props.searchInProgress });
        const thisRoomClasses = classNames({ mx_SearchBar_button: true, mx_SearchBar_unselected: this.state.scope !== 'Room' });
        const allRoomsClasses = classNames({ mx_SearchBar_button: true, mx_SearchBar_unselected: this.state.scope !== 'All' });

        return (

                <AccessibleButton className={ thisRoomClasses } onClick={this.onThisRoomClick}>{_t("This Room")}</AccessibleButton>
                <AccessibleButton className={ allRoomsClasses } onClick={this.onAllRoomsClick}>{_t("All Rooms")}</AccessibleButton>

                    <input ref="search_term" type="text" autoFocus={true} placeholder={_t("Search…")} onKeyDown={this.onSearchChange} />
                    <AccessibleButton className={ searchButtonClasses } onClick={this.onSearch}></AccessibleButton>

                <AccessibleButton className="mx_SearchBar_cancel" onClick={this.props.onCancelClick}></AccessibleButton>

        );

/*
const clearButton = this.state.searchTerm.length > 0 ?
            (<AccessibleButton key="button"
                    className="mx_SearchBox_closeButton"
                    onClick={ () => {this._clearSearch("button")} }>
            </AccessibleButton>) :  undefined;

        return (

                <input
                    key="searchfield"
                    type="text"
                    ref="search"
                    className="mx_textinput_icon mx_textinput_search"
                    value={ this.state.searchTerm }
                    onFocus={ this._onFocus }
                    onChange={ this.onChange }
                    onKeyDown={ this._onKeyDown }
                    placeholder={ _t('Filter room names') }
                />
                { clearButton }

        );
*/

    },
});
<commit_msg>remove commented out code from shifting things around<commit_after>/*
Copyright 2015, 2016 OpenMarket Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

'use strict';

const React = require('react');
const MatrixClientPeg = require('../../../MatrixClientPeg');
const sdk = require('../../../index');
const classNames = require('classnames');
const AccessibleButton = require('../../../components/views/elements/AccessibleButton');
import { _t } from '../../../languageHandler';

module.exports = React.createClass({
    displayName: 'SearchBar',

    getInitialState: function() {
        return ({
            scope: 'Room',
        });
    },

    onThisRoomClick: function() {
        this.setState({ scope: 'Room' }, () => this._searchIfQuery());
    },

    onAllRoomsClick: function() {
        this.setState({ scope: 'All' }, () => this._searchIfQuery());
    },

    onSearchChange: function(e) {
        if (e.keyCode === 13) { // on enter...
            this.onSearch();
        }
        if (e.keyCode === 27) { // escape...
            this.props.onCancelClick();
        }
    },

    _searchIfQuery: function() {
        if (this.refs.search_term.value) {
            this.onSearch();
        }
    },

    onSearch: function() {
        this.props.onSearch(this.refs.search_term.value, this.state.scope);
    },

    render: function() {
        const searchButtonClasses = classNames({ mx_SearchBar_searchButton: true, mx_SearchBar_searching: this.props.searchInProgress });
        const thisRoomClasses = classNames({ mx_SearchBar_button: true, mx_SearchBar_unselected: this.state.scope !== 'Room' });
        const allRoomsClasses = classNames({ mx_SearchBar_button: true, mx_SearchBar_unselected: this.state.scope !== 'All' });

        return (

                <AccessibleButton className={ thisRoomClasses } onClick={this.onThisRoomClick}>{_t("This Room")}</AccessibleButton>
                <AccessibleButton className={ allRoomsClasses } onClick={this.onAllRoomsClick}>{_t("All Rooms")}</AccessibleButton>

                    <input ref="search_term" type="text" autoFocus={true} placeholder={_t("Search…")} onKeyDown={this.onSearchChange} />
                    <AccessibleButton className={ searchButtonClasses } onClick={this.onSearch}></AccessibleButton>

                <AccessibleButton className="mx_SearchBar_cancel" onClick={this.props.onCancelClick}></AccessibleButton>

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


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

// * This function sets the playlist in the hash parameter of the URL

function setPlaylist() {
  if (videos.length > 1) {
    var playlist = JSON.stringify(videos);
    playlist = window.btoa(playlist);
    window.location.hash = playlist;
  }
  else {
    window.location.hash = "";
  }
  $("#saveButton").attr("data-clipboard-text", "https://lnfwebsite.github.io/Streamly/#" + playlist);
}

// * This function gets the playlist from the hash parameter of the URL
// * It loads videos into the videos array and the playlist viewer

function getPlaylist() {
  if (window.location.hash.substr(1) !== "") {
    var playlist = window.location.hash.substr(1);
    $("#saveButton").attr("data-clipboard-text", "https://lnfwebsite.github.io/Streamly/#" + playlist);
    try {
      playlist = window.atob(playlist);
      playlist = JSON.parse(playlist);
      videos = playlist;

      if (videos[0] !== undefined && videos[0] !== null) {
        $("#playlistNameBox").val(decodeURIComponent(videos[0]));
      }

      for (var i = 1; i < videos.length; i++) {
        videoCounter = i;
        var printTime = msConversion(videos[videoCounter][1] * 1000);
        addVideoToList(videos[videoCounter][0], printTime, videoCounter, true);
      }
      // -- Need to update the playlist with non-encoded stuff 10/04/2016
      setPlaylist();
      videoPaused = true;
      loopVideo();
    }
    catch(err) {
      alert("Uh oh... It looks like this playlist URL is broken, however, you may still be able to retrieve your data.\n\n" +
      "Make sure that you save the URL that you have now, and contact me (the administrator) by submitting an issue on Streamly's Github page.\n\n" +
      "I'm really sorry about this inconvenience.\n\nerr: " + err);
    }
  }
}

// * This function appends one playlist to another

function appendPlaylist(playlist) {
  try {
    playlist = window.atob(playlist);
    playlist = JSON.parse(playlist);

    if (playlist[0] !== undefined && playlist[0] !== null) {
      if (videos[0] === undefined || videos[0] === null) {
        $("#playlistNameBox").val(decodeURIComponent(playlist[0]));
        videos[0] = playlist[0];
      }
    }

    for (var i = 1; i < playlist.length; i++) {
      videoCounter++;
      var printTime = msConversion(playlist[i][1] * 1000);
      addVideoToList(playlist[i][0], printTime, videoCounter, true);
    }

    playlist.splice(0, 1);
    videos = videos.concat(playlist);

    setPlaylist();
  }
  catch(err) {
    alert("Uh oh... It looks like this playlist URL is broken, however, you may still be able to retrieve your data.\n\n" +
      "Make sure that you save the URL that you have now, and contact me (the administrator) by submitting an issue on Streamly's Github page.\n\n" +
      "I'm really sorry about this inconvenience.\n\nerr: " + err);
  }
}

// * This function shuffles the videos array for playlist shuffling

function shuffleArray(array) {
  for (var i = array.length - 1; i > 0; i--) {
    var j = Math.floor(Math.random() * (i + 1));
    var temp = array[i];
    array[i] = array[j];
    array[j] = temp;
  }
  return array;
}

// * This function shuffles the playlist
// * It's primary use is adding videos that were added during shuffle to the original playlist when un-shuffling

function shufflePlaylist() {
  var playlistName = videos[0];
  var videoIterationId = videos[videoIteration][2];
  var videosBeforeThisShuffle = [];
  if (playlistShuffle) {
    if (videosBeforeShuffle.length < 1) {
      videosBeforeShuffle = JSON.parse(JSON.stringify(videos));
    }
    videos.splice(0, 1);
    shuffleArray(videos);
    videos.unshift(playlistName);
  }
  else {
    if (addedVideosWhileShuffled.length > 0) {
      videosBeforeShuffle.push.apply(videosBeforeShuffle, addedVideosWhileShuffled);
      addedVideosWhileShuffled = [];
    }
    videos = JSON.parse(JSON.stringify(videosBeforeShuffle));
    videosBeforeShuffle = [];
    videos.splice(0, 1);
    videos.unshift(playlistName);
  }

  refreshVideoList();
  setPlaylist();
  makeSortable();
  videoPreviews();
  addAutoplayVideo();

  if (videos[videoIteration][2] !== videoIterationId) {
    videoIteration = changeIteration(-1);
    loopVideo();
  }
}

// * This is an added function to arrays for moving elements from one spot to another
// * It is primarily used for shuffling

Array.prototype.move = function(from, to) {
  this.splice(to, 0, this.splice(from, 1)[0]);
};

// * This function is called to move a video from one place in the playlist to another
// * It is called from the sortable function below and calls the Array.move additional functionality

function actionMoveVideo(oldIndex, newIndex) {
  sendStation("actionmovevideo," + oldIndex + "," + newIndex);
  videos.move(oldIndex, newIndex);
  if (oldIndex == videoIteration) {
    videoIteration = newIndex;
  }
  else if (oldIndex < videoIteration && newIndex >= videoIteration) {
    videoIteration = changeIteration(-1);
  }
  else if (oldIndex > videoIteration && newIndex <= videoIteration) {
    videoIteration = changeIteration(1);
  }
  addAutoplayVideo();
}
<commit_msg>need to run specialty functions after playlist append<commit_after>/**
  Copyright 2018 LNFWebsite
  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at
      http://www.apache.org/licenses/LICENSE-2.0
  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
**/

// * This function sets the playlist in the hash parameter of the URL

function setPlaylist() {
  if (videos.length > 1) {
    var playlist = JSON.stringify(videos);
    playlist = window.btoa(playlist);
    window.location.hash = playlist;
  }
  else {
    window.location.hash = "";
  }
  $("#saveButton").attr("data-clipboard-text", "https://lnfwebsite.github.io/Streamly/#" + playlist);
}

// * This function gets the playlist from the hash parameter of the URL
// * It loads videos into the videos array and the playlist viewer

function getPlaylist() {
  if (window.location.hash.substr(1) !== "") {
    var playlist = window.location.hash.substr(1);
    $("#saveButton").attr("data-clipboard-text", "https://lnfwebsite.github.io/Streamly/#" + playlist);
    try {
      playlist = window.atob(playlist);
      playlist = JSON.parse(playlist);
      videos = playlist;

      if (videos[0] !== undefined && videos[0] !== null) {
        $("#playlistNameBox").val(decodeURIComponent(videos[0]));
      }

      for (var i = 1; i < videos.length; i++) {
        videoCounter = i;
        var printTime = msConversion(videos[videoCounter][1] * 1000);
        addVideoToList(videos[videoCounter][0], printTime, videoCounter, true);
      }
      // -- Need to update the playlist with non-encoded stuff 10/04/2016
      setPlaylist();
      videoPaused = true;
      loopVideo();
    }
    catch(err) {
      alert("Uh oh... It looks like this playlist URL is broken, however, you may still be able to retrieve your data.\n\n" +
      "Make sure that you save the URL that you have now, and contact me (the administrator) by submitting an issue on Streamly's Github page.\n\n" +
      "I'm really sorry about this inconvenience.\n\nerr: " + err);
    }
  }
}

// * This function appends one playlist to another

function appendPlaylist(playlist) {
  try {
    playlist = window.atob(playlist);
    playlist = JSON.parse(playlist);

    if (playlist[0] !== undefined && playlist[0] !== null) {
      if (videos[0] === undefined || videos[0] === null) {
        $("#playlistNameBox").val(decodeURIComponent(playlist[0]));
        videos[0] = playlist[0];
      }
    }

    for (var i = 1; i < playlist.length; i++) {
      videoCounter++;
      var printTime = msConversion(playlist[i][1] * 1000);
      addVideoToList(playlist[i][0], printTime, videoCounter, true);
    }

    playlist.splice(0, 1);
    videos = videos.concat(playlist);

    setPlaylist();
    makeSortable();
    videoPreviews();
    addAutoplayVideo();
  }
  catch(err) {
    alert("Uh oh... It looks like this playlist URL is broken, however, you may still be able to retrieve your data.\n\n" +
      "Make sure that you save the URL that you have now, and contact me (the administrator) by submitting an issue on Streamly's Github page.\n\n" +
      "I'm really sorry about this inconvenience.\n\nerr: " + err);
  }
}

// * This function shuffles the videos array for playlist shuffling

function shuffleArray(array) {
  for (var i = array.length - 1; i > 0; i--) {
    var j = Math.floor(Math.random() * (i + 1));
    var temp = array[i];
    array[i] = array[j];
    array[j] = temp;
  }
  return array;
}

// * This function shuffles the playlist
// * It's primary use is adding videos that were added during shuffle to the original playlist when un-shuffling

function shufflePlaylist() {
  var playlistName = videos[0];
  var videoIterationId = videos[videoIteration][2];
  var videosBeforeThisShuffle = [];
  if (playlistShuffle) {
    if (videosBeforeShuffle.length < 1) {
      videosBeforeShuffle = JSON.parse(JSON.stringify(videos));
    }
    videos.splice(0, 1);
    shuffleArray(videos);
    videos.unshift(playlistName);
  }
  else {
    if (addedVideosWhileShuffled.length > 0) {
      videosBeforeShuffle.push.apply(videosBeforeShuffle, addedVideosWhileShuffled);
      addedVideosWhileShuffled = [];
    }
    videos = JSON.parse(JSON.stringify(videosBeforeShuffle));
    videosBeforeShuffle = [];
    videos.splice(0, 1);
    videos.unshift(playlistName);
  }

  refreshVideoList();
  setPlaylist();
  makeSortable();
  videoPreviews();
  addAutoplayVideo();

  if (videos[videoIteration][2] !== videoIterationId) {
    videoIteration = changeIteration(-1);
    loopVideo();
  }
}

// * This is an added function to arrays for moving elements from one spot to another
// * It is primarily used for shuffling

Array.prototype.move = function(from, to) {
  this.splice(to, 0, this.splice(from, 1)[0]);
};

// * This function is called to move a video from one place in the playlist to another
// * It is called from the sortable function below and calls the Array.move additional functionality

function actionMoveVideo(oldIndex, newIndex) {
  sendStation("actionmovevideo," + oldIndex + "," + newIndex);
  videos.move(oldIndex, newIndex);
  if (oldIndex == videoIteration) {
    videoIteration = newIndex;
  }
  else if (oldIndex < videoIteration && newIndex >= videoIteration) {
    videoIteration = changeIteration(-1);
  }
  else if (oldIndex > videoIteration && newIndex <= videoIteration) {
    videoIteration = changeIteration(1);
  }
  addAutoplayVideo();
}
<|endoftext|>


<commit_before>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

console.log('danmaku_rendering init');

function call_danmaku_rendering() {
	console.log('danmaku rendering get called.');
}<commit_msg>Danmu<commit_after>var win_Hei= $(window).height();//ڸ
var win_Wid= $(window).width();//ڿ
DM_init();

//Ԫ
$(window).resize(function () {
    win_Hei = $(window).height();
    win_Wid = $(window).width();
    $("#enter").css({
        "margin-top":win_Hei-100+"px",
        "margin-left":win_Wid/2-175+"px"
    })
});

//ʼĻ
function DM_init() {
    var init = {
        fontsize:'20px',
        fontcolor:'black',
        type:'left',
        };

    $("body").css({
        "overflow":"hidden"
        })
    .append("

");
    $("#DM").html("Ļ")
        .css({
            "position":"relative",
            "top":"0px",
            "left":"0px",
            "width":"100%",
            "height":"500px"
            });

    //
    $("body").append("

");
    $("#IN").append("

");
    $("#enter").css({
        "height":"100px",
        "width":"350px",
        "margin":"10px",
        "background":"azure",
        "bottom":"0px",
        "text-align":"center",
        "margin-top":win_Hei-100+"px",
        "margin-left":win_Wid/2-175+"px",
        "z-index":"0"
    });

    test();

};

function test(){
    var myDate = new Date();//ʱ
    var mytime=myDate.getTime();
    var data2 = {
        id:mytime,
        color:'red',
        text:'ԵĻ'+mytime
    };
var dm2 = createDM(data2);
    dm2.show();

}

function createDM(DM_data){
    var dm = new Object();
    dm.id = DM_data.id;
    dm.color = DM_data.color;
    dm.text = DM_data.text;
    //ʾĻ
    dm.show = function(){
    var select = "#"+dm.id;
    $("#DM").append("

");
    $(select).html(dm.text)
        .css({
        "position":"fixed",
        "float":"left",
        "left":win_Wid+"px",
        "font-size":20+"px",
        "color":dm.color,
        "margin-top":"10px",
        "margin-left":"30px",
        "width":"60px",
        "overflow":"visible",
        "white-space":"nowrap",
        "z-index":"900"
        })
    .stop(true,false).animate({'left':-200+'px'},8000,"linear",
        function(){$(select).remove()})
    };
    return dm;
};<|endoftext|>


<commit_before>//TODO: how to make this Acre-independant, so I can add to QUnit repository??

//var debug = function() {console.log.apply(this,arguments); };
var debug = function() {};

//WILL: should this really be file global?
var currentModule;

var currentFile = {
  file:acre.request.script.name,
  run_url: make_dev_url(acre.request.script.app.id) + '/' + acre.request.script.name,
  modules:[],
  total:null,
  failures:null
};

function _pushModule(name) {
  currentModule = { name:name, tests:[] };
  currentFile.modules.push(currentModule);
  return currentModule;
}

// create an inital dummy module, in case the first test isn't in a module
// we will throw this away if it's empty
var defaultModule = _pushModule('DEFAULT');

function extend(a, b) {
  for ( var prop in b ) {
    a[prop] = b[prop];
  }
  return a;
}

function get_app_path() {
  //TODO: what's the acre way to get the app_path?
  return '//' + (acre.request.server_name).replace('.'+acre.host.name,'');
}

function make_dev_url(app_path) {
    var protocol = acre.request.protocol;
    var port = acre.request.server_port;
    port = ((protocol == 'http' && port != 80) || (protocol == 'https' && port != 443)) ? ":" + port : '';
    return protocol + ":" + app_path + '.' + acre.host.name + port;
}

function currentTest() {
  var tests = currentModule.tests;
  return tests[tests.length-1];
}

function done(failures, total) {
  debug('done: failures='+failures+' total='+total);
  currentFile.failures = failures;
  currentFile.total    = total;
}

function log(result, message) {
  debug('log: ['+result+'] '+message);
  currentTest().log.push({result:result,message:message||'no comment'});
}

function testStart(name,testEnvironment) {
  debug('--testStart:'+name);
  // runtime is initally starttime
  currentModule.tests.push({name:name, runtime:+new Date(), testEnvironment:testEnvironment||null, log:[]});
}

function testDone(name, failures, total) {
  debug('--testDone:'+name+' failures='+failures+' total='+total);
  extend(currentTest(), {
    runtime:+new Date() - currentTest().runtime, // convert start time to runtime
    failures:failures,
    total:total
  });
}

function moduleStart(name) {
  debug('**moduleStart: '+name);
  _pushModule(name);
}

function moduleDone(name, failures, total) {
  debug('**moduleDone:  '+name+' failures='+failures+' total='+total);
  extend(currentModule, {
    failures:failures,
    total:total
  });
}

function _checkDefaultModule() {
  if (currentFile.modules.length>1 && defaultModule.tests.length===0) {
    // the user called module() before test(), so we can remove the defaultModule
    currentFile.modules.shift();
  } else {
    // count the results of the tests and store them in the default module
    defaultModule.failures=0;
    defaultModule.total=0;
    for (var i=0;i<defaultModule.tests.length;i++) {
      defaultModule.failures += defaultModule.tests[i].failures;
      defaultModule.total    += defaultModule.tests[i].total;
    }
  }
}

// duplicated in dashboard.sjs
function get_app_path() {
  //TODO: what's the acre way to get the app_path?
  return '//' + (acre.request.server_name).replace('.'+acre.host.name,'');
}

function report() {
  _checkDefaultModule(); // do we need to update the defaultModule?

  //only output if test was run directly (not required or run by /acre/test)
  if (!acre.request.params.test_self) { return; }

  var dashboard = acre.require('dashboard');
  var testsuite = {
    app_path:get_app_path(),
    tid:acre.request.headers['x-metaweb-tid'] || null, // missing on local acre server
    testprops:[], //HACK remove this
    testfiles:[currentFile],
    total:currentFile.total,
    failures:currentFile.failures
  };
  // console.error('HACK: report testsuite',testsuite );
  dashboard.show_suite(testsuite);
}

function add_log_hooks(testfile) {
  extend(testfile.acre.test,{
    done        : done,
    log         : log,
    testStart   : testStart,
    testDone    : testDone,
    moduleStart : moduleStart,
    moduleDone  : moduleDone,
    report      : report      //WILL: new api?
  });
}

<commit_msg>fixed test<commit_after>//TODO: how to make this Acre-independant, so I can add to QUnit repository??

//var debug = function() {console.log.apply(this,arguments); };
var debug = function() {};

//WILL: should this really be file global?
var currentModule;

var currentFile = {
  file:acre.request.script.name,
  run_url: make_dev_url(get_app_path()) + '/' + acre.request.script.name,
  modules:[],
  total:null,
  failures:null
};

function _pushModule(name) {
  currentModule = { name:name, tests:[] };
  currentFile.modules.push(currentModule);
  return currentModule;
}

// create an inital dummy module, in case the first test isn't in a module
// we will throw this away if it's empty
var defaultModule = _pushModule('DEFAULT');

function extend(a, b) {
  for ( var prop in b ) {
    a[prop] = b[prop];
  }
  return a;
}

function get_app_path() {
  //TODO: what's the acre way to get the app_path?
  return '//' + (acre.request.server_name).replace('.'+acre.host.name,'');
}

function make_dev_url(app_path) {
    var protocol = acre.request.protocol;
    var port = acre.request.server_port;
    port = ((protocol == 'http' && port != 80) || (protocol == 'https' && port != 443)) ? ":" + port : '';
    return protocol + ":" + app_path + '.' + acre.host.name + port;
}

function currentTest() {
  var tests = currentModule.tests;
  return tests[tests.length-1];
}

function done(failures, total) {
  debug('done: failures='+failures+' total='+total);
  currentFile.failures = failures;
  currentFile.total    = total;
}

function log(result, message) {
  debug('log: ['+result+'] '+message);
  currentTest().log.push({result:result,message:message||'no comment'});
}

function testStart(name,testEnvironment) {
  debug('--testStart:'+name);
  // runtime is initally starttime
  currentModule.tests.push({name:name, runtime:+new Date(), testEnvironment:testEnvironment||null, log:[]});
}

function testDone(name, failures, total) {
  debug('--testDone:'+name+' failures='+failures+' total='+total);
  extend(currentTest(), {
    runtime:+new Date() - currentTest().runtime, // convert start time to runtime
    failures:failures,
    total:total
  });
}

function moduleStart(name) {
  debug('**moduleStart: '+name);
  _pushModule(name);
}

function moduleDone(name, failures, total) {
  debug('**moduleDone:  '+name+' failures='+failures+' total='+total);
  extend(currentModule, {
    failures:failures,
    total:total
  });
}

function _checkDefaultModule() {
  if (currentFile.modules.length>1 && defaultModule.tests.length===0) {
    // the user called module() before test(), so we can remove the defaultModule
    currentFile.modules.shift();
  } else {
    // count the results of the tests and store them in the default module
    defaultModule.failures=0;
    defaultModule.total=0;
    for (var i=0;i<defaultModule.tests.length;i++) {
      defaultModule.failures += defaultModule.tests[i].failures;
      defaultModule.total    += defaultModule.tests[i].total;
    }
  }
}

// duplicated in dashboard.sjs
function get_app_path() {
  //TODO: what's the acre way to get the app_path?
  return '//' + (acre.request.server_name).replace('.'+acre.host.name,'');
}

function report() {
  _checkDefaultModule(); // do we need to update the defaultModule?

  //only output if test was run directly (not required or run by /acre/test)
  if (!acre.request.params.test_self) { return; }

  var dashboard = acre.require('dashboard');
  var testsuite = {
    app_path:get_app_path(),
    tid:acre.request.headers['x-metaweb-tid'] || null, // missing on local acre server
    testprops:[], //HACK remove this
    testfiles:[currentFile],
    total:currentFile.total,
    failures:currentFile.failures
  };
  // console.error('HACK: report testsuite',testsuite );
  dashboard.show_suite(testsuite);
}

function add_log_hooks(testfile) {
  extend(testfile.acre.test,{
    done        : done,
    log         : log,
    testStart   : testStart,
    testDone    : testDone,
    moduleStart : moduleStart,
    moduleDone  : moduleDone,
    report      : report      //WILL: new api?
  });
}

<|endoftext|>


<commit_before>'use strict';
const dispatcher = thorin.dispatcher;
/**
 * This contains all functionality required by the registry
 * to work with services, including the heartbeat, service registration and such.
 */
/*
 * Returns the state of the registry.
 * */
let reqCount = 0;
setInterval(() => {
  if (reqCount === 0) return;
  let reqPerSec = parseFloat(reqCount / 3600).toFixed(2);
  log.info(`Registry requests per hour: ${reqCount} [${reqPerSec}/sec]`);
  reqCount = 0;
}, 60 * 60 * 1000); // once an hour, we reset the request count.

dispatcher
  .addAction('registry.get')
  .template('registry')
  .debug(false)
  .input({
    type: dispatcher.validate('STRING').default(null),
    tags: dispatcher.validate('ARRAY', {type: 'string'}).default(null)
  })
  .alias('GET', '/registry')
  .use('registry.saveChanges')
  .use((intentObj, next) => {
    const registry = intentObj.data('registry'),
      storeObj = thorin.lib('store'),
      accessToken = intentObj.data('token'),
      regEnv = intentObj.data('registry_env'),
      filterType = intentObj.input('type'),
      filterTags = intentObj.input('tags'),
      filterTagMap = {};
    let resultData = [];
    if (filterTags) {
      for (let j = 0; j < filterTags.length; j++) {
        filterTagMap[filterTags[j]] = true;
      }
    }
    let maxMap = {};  // a map of {serviceType:versionNumber}
    for (let i = 0, len = registry.length; i < len; i++) {
      let item = registry[i];
      if (filterType && item.type !== filterType) continue;
      if (filterTags) {
        let isTagFound = false;
        for (let j = 0; j < item.tags.length; j++) {
          if (filterTagMap[item.tags[j]]) {
            isTagFound = true;
            break;
          }
        }
        if (!isTagFound) continue;
      }
      if (item.env !== regEnv) continue;
      if (typeof item.version !== 'number') {
        if (typeof item.version !== 'undefined') delete item.version;
      } else {
        if (typeof maxMap[item.type] === 'undefined' || maxMap[item.type] < item.version) {
          maxMap[item.type] = item.version;
        }
      }
      delete item.id;
      delete item.env;
      resultData.push(item);
    }

    // finally, we check the version of each one, if available.
    let fullData = [];
    for (let i = 0, len = resultData.length; i < len; i++) {
      let item = resultData[i];
      if (typeof item.version === 'undefined') {
        fullData.push(item);
        continue;
      }
      if (typeof maxMap[item.type] !== 'undefined') {
        if (item.version < maxMap[item.type]) continue;
      }
      fullData.push(item);
    }
    resultData = fullData;
    let calls = [];

    calls.push(() => {
      return storeObj.getServiceKey(accessToken).then((serviceKey) => {
        intentObj.setMeta({
          service_key: serviceKey
        });
      });
    });
    thorin.series(calls, (e) => {
      if (e) return next(e);
      intentObj.result(resultData);
      intentObj.send();
      reqCount++;
    });
  });

/*
 * Registers a new service to the registry.
 * Note that services are unique by their host:port.
 * */
dispatcher
  .addAction('registry.announce')
  .template('registry')
  .alias('POST', '/announce')
  .debug(false)
  .input({
    type: dispatcher.validate('STRING').error('TYPE.MISSING', 'Missing microservice type', 400),
    name: dispatcher.validate('STRING').default(null),  // the microservice name.
    host: dispatcher.validate('STRING').error('HOST.MISSING', 'Missing microservice host address', 400),
    proto: dispatcher.validate('ENUM', ['http', 'https', 'ws', 'wss', 'tcp']).default('http'),
    timeout: dispatcher.validate('NUMBER', {min: 100}).default(null),
    port: dispatcher.validate('NUMBER', {
      float: false,
      min: 1,
      max: 65536
    }).error('PORT.MISSING', 'Missing microservice port', 400),
    path: dispatcher.validate('STRING').default(''), // the default host path
    tags: dispatcher.validate('ARRAY', {type: 'string'}).default([]),
    ttl: dispatcher.validate('NUMBER', {min: 1, max: 120}).default(30),  // default TTL of the service before we remove it, in seconds
    version: dispatcher.validate('NUMBER', {min: 0}).default(null)
  })
  .use((intentObj, next) => {
    const serviceData = intentObj.input(),
      accessToken = intentObj.data('token'),
      serviceId = serviceData.host + ':' + serviceData.port,
      registryData = intentObj.data('registry'),
      storeObj = thorin.lib('store');
    if (serviceData.version == null) delete serviceData.version;
    serviceData.env = intentObj.data('registry_env');
    if (serviceData.timeout == null) delete serviceData.timeout;
    let wasFound = false, sid;
    // Step one, check if the registry already contains the new service host:port
    for (let i = 0, len = registryData.length; i < len; i++) {
      let item = registryData[i];
      if (item.id === serviceId && item.env === intentObj.data('registry_env')) {
        wasFound = true;
        sid = item.sid;
        // override any data
        item.tags = serviceData.tags;
        item.ttl = serviceData.ttl;
        if (typeof serviceData.version === 'number') item.version = serviceData.version;
        if (serviceData.name) item.name = serviceData.name;
        if (serviceData.type !== item.type) item.type = serviceData.type;
        // update the remove_at ts.
        item.remove_at = Date.now() + serviceData.ttl * 1000;
        break;
      }
    }
    // If we did not find it, we push it.
    if (!wasFound) {
      serviceData.remove_at = Date.now() + serviceData.ttl * 1000;
      serviceData.id = serviceId; // this is the host:port id
      serviceData.sid = thorin.util.sha1(thorin.util.randomString(32) + serviceData.id); // this is the heartbeat id
      sid = serviceData.sid;
      registryData.push(serviceData);
    }
    let calls = [],
      serviceKey;
    // step one, update the registry
    calls.push(() => {
      return storeObj.saveRegistry(accessToken, registryData);
    });
    // next, fetch the service shared key.
    calls.push(() => {
      return storeObj.getServiceKey(accessToken).then((key) => {
        serviceKey = key;
      });
    });
    thorin.series(calls, (err) => {
      if (err) return next(err);
      let resultData = [];
      /*
       * We now have to check for the version number. If it is specified, any older versions of the same
       * app type will be removed, so that traffic will not be directed to it anymore.
       * */
      let maxMap = {};  // a map of {serviceType:versionNumber}
      for (let i = 0, len = registryData.length; i < len; i++) {
        let item = registryData[i];
        if (typeof item.version !== 'number') {
          if (typeof item.version !== 'undefined') delete item.version;
          continue;
        }
        if (typeof maxMap[item.type] === 'undefined' || maxMap[item.type] < item.version) {
          maxMap[item.type] = item.version;
        }
      }
      // finally, we will return the entire registry.
      for (let i = 0, len = registryData.length; i < len; i++) {
        let item = registryData[i];
        if (item.env !== intentObj.data('registry_env')) continue;
        if (typeof maxMap[item.type] !== 'undefined') {
          if (typeof item.version === 'undefined' || item.version < maxMap[item.type]) continue;
        }
        delete item.env;
        delete item.id;
        resultData.push(item);
      }
      intentObj.result(resultData);
      intentObj.setMeta({
        sid: sid,
        service_key: serviceKey
      });
      reqCount++;
      next();
    });
  });

/*
 * Manually leave the registry
 * */
dispatcher
  .addAction('registry.leave')
  .template('registry')
  .alias('POST', '/leave')
  .input({
    sid: dispatcher.validate('STRING').error('SID.MISSING', 'Missing sid')
  })
  .use((intentObj, next) => {
    const sid = intentObj.input('sid'),
      registry = intentObj.data('registry'),
      accessToken = intentObj.data('token'),
      env = intentObj.data('registry_env'),
      storeObj = thorin.lib('store');

    // check if we remove it.
    let wasFound = false;
    for (let i = 0, len = registry.length; i < len; i++) {
      let item = registry[i];
      if (item.env !== env) continue;
      if (item.sid === sid) {
        registry.splice(i, 1);
        wasFound = true;
        break;
      }
    }
    if (!wasFound) {
      return intentObj.send();
    }
    storeObj
      .saveRegistry(accessToken, registry)
      .then(() => intentObj.send())
      .catch((e) => next(e));
  });<commit_msg>fix version<commit_after>'use strict';
const dispatcher = thorin.dispatcher;
/**
 * This contains all functionality required by the registry
 * to work with services, including the heartbeat, service registration and such.
 */
/*
 * Returns the state of the registry.
 * */
let reqCount = 0;
setInterval(() => {
  if (reqCount === 0) return;
  let reqPerSec = parseFloat(reqCount / 3600).toFixed(2);
  log.info(`Registry requests per hour: ${reqCount} [${reqPerSec}/sec]`);
  reqCount = 0;
}, 60 * 60 * 1000); // once an hour, we reset the request count.

dispatcher
  .addAction('registry.get')
  .template('registry')
  .debug(false)
  .input({
    type: dispatcher.validate('STRING').default(null),
    tags: dispatcher.validate('ARRAY', {type: 'string'}).default(null)
  })
  .alias('GET', '/registry')
  .use('registry.saveChanges')
  .use((intentObj, next) => {
    const registry = intentObj.data('registry'),
      storeObj = thorin.lib('store'),
      accessToken = intentObj.data('token'),
      regEnv = intentObj.data('registry_env'),
      filterType = intentObj.input('type'),
      filterTags = intentObj.input('tags'),
      filterTagMap = {};
    let resultData = [];
    if (filterTags) {
      for (let j = 0; j < filterTags.length; j++) {
        filterTagMap[filterTags[j]] = true;
      }
    }
    let maxMap = {};  // a map of {serviceType:versionNumber}
    for (let i = 0, len = registry.length; i < len; i++) {
      let item = registry[i];
      if (filterType && item.type !== filterType) continue;
      if (filterTags) {
        let isTagFound = false;
        for (let j = 0; j < item.tags.length; j++) {
          if (filterTagMap[item.tags[j]]) {
            isTagFound = true;
            break;
          }
        }
        if (!isTagFound) continue;
      }
      if (item.env !== regEnv) continue;
      if (typeof item.version !== 'number') {
        if (typeof item.version !== 'undefined') delete item.version;
      } else {
        if (typeof maxMap[item.type] === 'undefined' || maxMap[item.type] < item.version) {
          maxMap[item.type] = item.version;
        }
      }
      delete item.id;
      delete item.env;
      resultData.push(item);
    }

    // finally, we check the version of each one, if available.
    let fullData = [];
    for (let i = 0, len = resultData.length; i < len; i++) {
      let item = resultData[i];
      if (typeof item.version === 'undefined') {
        fullData.push(item);
        continue;
      }
      if (typeof maxMap[item.type] !== 'undefined') {
        if (item.version < maxMap[item.type]) continue;
      }
      fullData.push(item);
    }
    resultData = fullData;
    let calls = [];

    calls.push(() => {
      return storeObj.getServiceKey(accessToken).then((serviceKey) => {
        intentObj.setMeta({
          service_key: serviceKey
        });
      });
    });
    thorin.series(calls, (e) => {
      if (e) return next(e);
      intentObj.result(resultData);
      intentObj.send();
      reqCount++;
    });
  });

/*
 * Registers a new service to the registry.
 * Note that services are unique by their host:port.
 * */
dispatcher
  .addAction('registry.announce')
  .template('registry')
  .alias('POST', '/announce')
  .debug(false)
  .input({
    type: dispatcher.validate('STRING').error('TYPE.MISSING', 'Missing microservice type', 400),
    name: dispatcher.validate('STRING').default(null),  // the microservice name.
    host: dispatcher.validate('STRING').error('HOST.MISSING', 'Missing microservice host address', 400),
    proto: dispatcher.validate('ENUM', ['http', 'https', 'ws', 'wss', 'tcp']).default('http'),
    timeout: dispatcher.validate('NUMBER', {min: 100}).default(null),
    port: dispatcher.validate('NUMBER', {
      float: false,
      min: 1,
      max: 65536
    }).error('PORT.MISSING', 'Missing microservice port', 400),
    path: dispatcher.validate('STRING').default(''), // the default host path
    tags: dispatcher.validate('ARRAY', {type: 'string'}).default([]),
    ttl: dispatcher.validate('NUMBER', {min: 1, max: 120}).default(30),  // default TTL of the service before we remove it, in seconds
    version: dispatcher.validate('NUMBER', {min: 0}).default(null)
  })
  .use((intentObj, next) => {
    const serviceData = intentObj.input(),
      accessToken = intentObj.data('token'),
      serviceId = serviceData.host + ':' + serviceData.port,
      registryData = intentObj.data('registry'),
      storeObj = thorin.lib('store');
    if (serviceData.version == null) delete serviceData.version;
    serviceData.env = intentObj.data('registry_env');
    if (serviceData.timeout == null) delete serviceData.timeout;
    let wasFound = false, sid;
    // Step one, check if the registry already contains the new service host:port
    for (let i = 0, len = registryData.length; i < len; i++) {
      let item = registryData[i];
      if (item.id === serviceId && item.env === intentObj.data('registry_env')) {
        wasFound = true;
        sid = item.sid;
        // override any data
        item.tags = serviceData.tags;
        item.ttl = serviceData.ttl;
        if (typeof serviceData.version === 'number') {
          item.version = serviceData.version;
        } else if (typeof item.version !== 'undefined') {
          delete item.version;
        }
        if (serviceData.name) item.name = serviceData.name;
        if (serviceData.type !== item.type) item.type = serviceData.type;
        // update the remove_at ts.
        item.remove_at = Date.now() + serviceData.ttl * 1000;
        break;
      }
    }
    // If we did not find it, we push it.
    if (!wasFound) {
      serviceData.remove_at = Date.now() + serviceData.ttl * 1000;
      serviceData.id = serviceId; // this is the host:port id
      serviceData.sid = thorin.util.sha1(thorin.util.randomString(32) + serviceData.id); // this is the heartbeat id
      sid = serviceData.sid;
      registryData.push(serviceData);
    }
    let calls = [],
      serviceKey;
    // step one, update the registry
    calls.push(() => {
      return storeObj.saveRegistry(accessToken, registryData);
    });
    // next, fetch the service shared key.
    calls.push(() => {
      return storeObj.getServiceKey(accessToken).then((key) => {
        serviceKey = key;
      });
    });
    thorin.series(calls, (err) => {
      if (err) return next(err);
      let resultData = [];
      /*
       * We now have to check for the version number. If it is specified, any older versions of the same
       * app type will be removed, so that traffic will not be directed to it anymore.
       * */
      let maxMap = {};  // a map of {serviceType:versionNumber}
      for (let i = 0, len = registryData.length; i < len; i++) {
        let item = registryData[i];
        if (typeof item.version !== 'number') {
          if (typeof item.version !== 'undefined') delete item.version;
          continue;
        }
        if (typeof maxMap[item.type] === 'undefined' || maxMap[item.type] < item.version) {
          maxMap[item.type] = item.version;
        }
      }
      // finally, we will return the entire registry.
      for (let i = 0, len = registryData.length; i < len; i++) {
        let item = registryData[i];
        if (item.env !== intentObj.data('registry_env')) continue;
        if (typeof maxMap[item.type] !== 'undefined') {
          if (typeof item.version === 'undefined' || item.version < maxMap[item.type]) continue;
        }
        delete item.env;
        delete item.id;
        resultData.push(item);
      }
      intentObj.result(resultData);
      intentObj.setMeta({
        sid: sid,
        service_key: serviceKey
      });
      reqCount++;
      next();
    });
  });

/*
 * Manually leave the registry
 * */
dispatcher
  .addAction('registry.leave')
  .template('registry')
  .alias('POST', '/leave')
  .input({
    sid: dispatcher.validate('STRING').error('SID.MISSING', 'Missing sid')
  })
  .use((intentObj, next) => {
    const sid = intentObj.input('sid'),
      registry = intentObj.data('registry'),
      accessToken = intentObj.data('token'),
      env = intentObj.data('registry_env'),
      storeObj = thorin.lib('store');

    // check if we remove it.
    let wasFound = false;
    for (let i = 0, len = registry.length; i < len; i++) {
      let item = registry[i];
      if (item.env !== env) continue;
      if (item.sid === sid) {
        registry.splice(i, 1);
        wasFound = true;
        break;
      }
    }
    if (!wasFound) {
      return intentObj.send();
    }
    storeObj
      .saveRegistry(accessToken, registry)
      .then(() => intentObj.send())
      .catch((e) => next(e));
  });<|endoftext|>