<commit_before>import Presence from 'discourse/mixins/presence';
import ModalFunctionality from 'discourse/mixins/modal-functionality';
import ObjectController from 'discourse/controllers/object';

export default ObjectController.extend(Presence, ModalFunctionality, {
  needs: ['user-invited-show'],

  // If this isn't defined, it will proxy to the user model on the preferences
  // page which is wrong.
  emailOrUsername: null,

  isAdmin: function(){
    return Discourse.User.currentProp("admin");
  }.property(),

  disabled: function() {
    if (this.get('saving')) return true;
    if (this.blank('emailOrUsername')) return true;
    const emailOrUsername = this.get('emailOrUsername').trim();
    // when inviting to forum, email must be valid
    if (!this.get('invitingToTopic') && !Discourse.Utilities.emailValid(emailOrUsername)) return true;
    // normal users (not admin) can't invite users to private topic via email
    if (!this.get('isAdmin') && this.get('isPrivateTopic') && Discourse.Utilities.emailValid(emailOrUsername)) return true;
    // when invting to private topic via email, group name must be specified
    if (this.get('isPrivateTopic') && this.blank('groupNames') && Discourse.Utilities.emailValid(emailOrUsername)) return true;
    if (this.get('model.details.can_invite_to')) return false;
    return false;
  }.property('isAdmin', 'emailOrUsername', 'invitingToTopic', 'isPrivateTopic', 'groupNames', 'saving'),

  buttonTitle: function() {
    return this.get('saving') ? I18n.t('topic.inviting') : I18n.t('topic.invite_reply.action');
  }.property('saving'),

  // We are inviting to a topic if the model isn't the current user.
  // The current user would mean we are inviting to the forum in general.
  invitingToTopic: function() {
    return this.get('model') !== Discourse.User.current();
  }.property('model'),

  topicId: Ember.computed.alias('model.id'),

  // Is Private Topic? (i.e. visible only to specific group members)
  isPrivateTopic: Em.computed.and('invitingToTopic', 'model.category.read_restricted'),

  // Is Private Message?
  isMessage: Em.computed.equal('model.archetype', 'private_message'),

  // Allow Existing Members? (username autocomplete)
  allowExistingMembers: function() {
    return this.get('invitingToTopic');
  }.property('invitingToTopic'),

  // Show Groups? (add invited user to private group)
  showGroups: function() {
    return this.get('isAdmin') && (Discourse.Utilities.emailValid(this.get('emailOrUsername')) || this.get('isPrivateTopic') || !this.get('invitingToTopic')) && !Discourse.SiteSettings.enable_sso && !this.get('isMessage');
  }.property('isAdmin', 'emailOrUsername', 'isPrivateTopic', 'isMessage', 'invitingToTopic'),

  // Instructional text for the modal.
  inviteInstructions: function() {
    if (Discourse.SiteSettings.enable_sso) {
      // inviting existing user when SSO enabled
      return I18n.t('topic.invite_reply.sso_enabled');
    } else if (this.get('isMessage')) {
      // inviting to a message
      return I18n.t('topic.invite_private.email_or_username');
    } else if (this.get('invitingToTopic')) {
      // inviting to a private/public topic
      if (this.get('isPrivateTopic') && !this.get('isAdmin')) {
        // inviting to a private topic and is not admin
        return I18n.t('topic.invite_reply.to_username');
      } else {
        // when inviting to a topic, display instructions based on provided entity
        if (this.blank('emailOrUsername')) {
          return I18n.t('topic.invite_reply.to_topic_blank');
        } else if (Discourse.Utilities.emailValid(this.get('emailOrUsername'))) {
          return I18n.t('topic.invite_reply.to_topic_email');
        } else {
          return I18n.t('topic.invite_reply.to_topic_username');
        }
      }
    } else {
      // inviting to forum
      return I18n.t('topic.invite_reply.to_forum');
    }
  }.property('isMessage', 'invitingToTopic', 'emailOrUsername'),

  // Instructional text for the group selection.
  groupInstructions: function() {
    return this.get('isPrivateTopic') ?
            I18n.t('topic.automatically_add_to_groups_required') :
            I18n.t('topic.automatically_add_to_groups_optional');
  }.property('isPrivateTopic'),

  groupFinder(term) {
    return Discourse.Group.findAll({search: term, ignore_automatic: true});
  },

  successMessage: function() {
    if (this.get('isMessage')) {
      return I18n.t('topic.invite_private.success');
    } else if ( Discourse.Utilities.emailValid(this.get('emailOrUsername')) ) {
      return I18n.t('topic.invite_reply.success_email', { emailOrUsername: this.get('emailOrUsername') });
    } else {
      return I18n.t('topic.invite_reply.success_username');
    }
  }.property('isMessage', 'emailOrUsername'),

  errorMessage: function() {
    return this.get('isMessage') ? I18n.t('topic.invite_private.error') : I18n.t('topic.invite_reply.error');
  }.property('isMessage'),

  placeholderKey: function() {
    return Discourse.SiteSettings.enable_sso ?
            'topic.invite_reply.username_placeholder' :
            'topic.invite_private.email_or_username_placeholder';
  }.property(),

  // Reset the modal to allow a new user to be invited.
  reset() {
    this.setProperties({
      emailOrUsername: null,
      groupNames: null,
      error: false,
      saving: false,
      finished: false
    });
  },

  actions: {

    createInvite() {
      if (this.get('disabled')) { return; }

      const groupNames = this.get('groupNames'),
            userInvitedController = this.get('controllers.user-invited-show');

      this.setProperties({ saving: true, error: false });

      return this.get('model').createInvite(this.get('emailOrUsername').trim(), groupNames).then(result => {
              this.setProperties({ saving: false, finished: true });
              if (!this.get('invitingToTopic')) {
                Discourse.Invite.findInvitedBy(Discourse.User.current(), userInvitedController.get('filter')).then(invite_model => {
                  userInvitedController.set('model', invite_model);
                  userInvitedController.set('totalInvites', invite_model.invites.length);
                });
              } else if (this.get('isMessage') && result && result.user) {
                this.get('model.details.allowed_users').pushObject(result.user);
              }
            }).catch(() => this.setProperties({ saving: false, error: true }));
    }
  }

});
<commit_msg>optimize invite js code<commit_after>import Presence from 'discourse/mixins/presence';
import ModalFunctionality from 'discourse/mixins/modal-functionality';
import ObjectController from 'discourse/controllers/object';

export default ObjectController.extend(Presence, ModalFunctionality, {
  needs: ['user-invited-show'],

  // If this isn't defined, it will proxy to the user model on the preferences
  // page which is wrong.
  emailOrUsername: null,

  isAdmin: function(){
    return Discourse.User.currentProp("admin");
  }.property(),

  disabled: function() {
    if (this.get('saving')) return true;
    if (this.blank('emailOrUsername')) return true;
    const emailOrUsername = this.get('emailOrUsername').trim();
    // when inviting to forum, email must be valid
    if (!this.get('invitingToTopic') && !Discourse.Utilities.emailValid(emailOrUsername)) return true;
    // normal users (not admin) can't invite users to private topic via email
    if (!this.get('isAdmin') && this.get('isPrivateTopic') && Discourse.Utilities.emailValid(emailOrUsername)) return true;
    // when invting to private topic via email, group name must be specified
    if (this.get('isPrivateTopic') && this.blank('groupNames') && Discourse.Utilities.emailValid(emailOrUsername)) return true;
    if (this.get('model.details.can_invite_to')) return false;
    return false;
  }.property('isAdmin', 'emailOrUsername', 'invitingToTopic', 'isPrivateTopic', 'groupNames', 'saving'),

  buttonTitle: function() {
    return this.get('saving') ? I18n.t('topic.inviting') : I18n.t('topic.invite_reply.action');
  }.property('saving'),

  // We are inviting to a topic if the model isn't the current user.
  // The current user would mean we are inviting to the forum in general.
  invitingToTopic: function() {
    return this.get('model') !== this.currentUser;
  }.property('model'),

  topicId: Ember.computed.alias('model.id'),

  // Is Private Topic? (i.e. visible only to specific group members)
  isPrivateTopic: Em.computed.and('invitingToTopic', 'model.category.read_restricted'),

  // Is Private Message?
  isMessage: Em.computed.equal('model.archetype', 'private_message'),

  // Allow Existing Members? (username autocomplete)
  allowExistingMembers: function() {
    return this.get('invitingToTopic');
  }.property('invitingToTopic'),

  // Show Groups? (add invited user to private group)
  showGroups: function() {
    return this.get('isAdmin') && (Discourse.Utilities.emailValid(this.get('emailOrUsername')) || this.get('isPrivateTopic') || !this.get('invitingToTopic')) && !Discourse.SiteSettings.enable_sso && !this.get('isMessage');
  }.property('isAdmin', 'emailOrUsername', 'isPrivateTopic', 'isMessage', 'invitingToTopic'),

  // Instructional text for the modal.
  inviteInstructions: function() {
    if (Discourse.SiteSettings.enable_sso) {
      // inviting existing user when SSO enabled
      return I18n.t('topic.invite_reply.sso_enabled');
    } else if (this.get('isMessage')) {
      // inviting to a message
      return I18n.t('topic.invite_private.email_or_username');
    } else if (this.get('invitingToTopic')) {
      // inviting to a private/public topic
      if (this.get('isPrivateTopic') && !this.get('isAdmin')) {
        // inviting to a private topic and is not admin
        return I18n.t('topic.invite_reply.to_username');
      } else {
        // when inviting to a topic, display instructions based on provided entity
        if (this.blank('emailOrUsername')) {
          return I18n.t('topic.invite_reply.to_topic_blank');
        } else if (Discourse.Utilities.emailValid(this.get('emailOrUsername'))) {
          return I18n.t('topic.invite_reply.to_topic_email');
        } else {
          return I18n.t('topic.invite_reply.to_topic_username');
        }
      }
    } else {
      // inviting to forum
      return I18n.t('topic.invite_reply.to_forum');
    }
  }.property('isMessage', 'invitingToTopic', 'emailOrUsername'),

  // Instructional text for the group selection.
  groupInstructions: function() {
    return this.get('isPrivateTopic') ?
            I18n.t('topic.automatically_add_to_groups_required') :
            I18n.t('topic.automatically_add_to_groups_optional');
  }.property('isPrivateTopic'),

  groupFinder(term) {
    return Discourse.Group.findAll({search: term, ignore_automatic: true});
  },

  successMessage: function() {
    if (this.get('isMessage')) {
      return I18n.t('topic.invite_private.success');
    } else if ( Discourse.Utilities.emailValid(this.get('emailOrUsername')) ) {
      return I18n.t('topic.invite_reply.success_email', { emailOrUsername: this.get('emailOrUsername') });
    } else {
      return I18n.t('topic.invite_reply.success_username');
    }
  }.property('isMessage', 'emailOrUsername'),

  errorMessage: function() {
    return this.get('isMessage') ? I18n.t('topic.invite_private.error') : I18n.t('topic.invite_reply.error');
  }.property('isMessage'),

  placeholderKey: function() {
    return Discourse.SiteSettings.enable_sso ?
            'topic.invite_reply.username_placeholder' :
            'topic.invite_private.email_or_username_placeholder';
  }.property(),

  // Reset the modal to allow a new user to be invited.
  reset() {
    this.setProperties({
      emailOrUsername: null,
      groupNames: null,
      error: false,
      saving: false,
      finished: false
    });
  },

  actions: {

    createInvite() {
      if (this.get('disabled')) { return; }

      const groupNames = this.get('groupNames'),
            userInvitedController = this.get('controllers.user-invited-show');

      this.setProperties({ saving: true, error: false });

      return this.get('model').createInvite(this.get('emailOrUsername').trim(), groupNames).then(result => {
              this.setProperties({ saving: false, finished: true });
              if (!this.get('invitingToTopic')) {
                Discourse.Invite.findInvitedBy(this.currentUser, userInvitedController.get('filter')).then(invite_model => {
                  userInvitedController.set('model', invite_model);
                  userInvitedController.set('totalInvites', invite_model.invites.length);
                });
              } else if (this.get('isMessage') && result && result.user) {
                this.get('model.details.allowed_users').pushObject(result.user);
              }
            }).catch(() => this.setProperties({ saving: false, error: true }));
    }
  }

});
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import Immutable from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { FormattedMessage } from 'react-intl';
import punycode from 'punycode';
import classnames from 'classnames';
import Icon from 'mastodon/components/icon';
import { useBlurhash } from 'mastodon/initial_state';
import { decode } from 'blurhash';
import { debounce } from 'lodash';

const IDNA_PREFIX = 'xn--';

const decodeIDNA = domain => {
  return domain
    .split('.')
    .map(part => part.indexOf(IDNA_PREFIX) === 0 ? punycode.decode(part.slice(IDNA_PREFIX.length)) : part)
    .join('.');
};

const getHostname = url => {
  const parser = document.createElement('a');
  parser.href = url;
  return parser.hostname;
};

const trim = (text, len) => {
  const cut = text.indexOf(' ', len);

  if (cut === -1) {
    return text;
  }

  return text.substring(0, cut) + (text.length > len ? '…' : '');
};

const domParser = new DOMParser();

const addAutoPlay = html => {
  const document = domParser.parseFromString(html, 'text/html').documentElement;
  const iframe = document.querySelector('iframe');

  if (iframe) {
    if (iframe.src.indexOf('?') !== -1) {
      iframe.src += '&';
    } else {
      iframe.src += '?';
    }

    iframe.src += 'autoplay=1&auto_play=1';

    // DOM parser creates html/body elements around original HTML fragment,
    // so we need to get innerHTML out of the body and not the entire document
    return document.querySelector('body').innerHTML;
  }

  return html;
};

export default class Card extends React.PureComponent {

  static propTypes = {
    card: ImmutablePropTypes.map,
    maxDescription: PropTypes.number,
    onOpenMedia: PropTypes.func.isRequired,
    compact: PropTypes.bool,
    defaultWidth: PropTypes.number,
    cacheWidth: PropTypes.func,
    sensitive: PropTypes.bool,
  };

  static defaultProps = {
    maxDescription: 50,
    compact: false,
  };

  state = {
    width: this.props.defaultWidth || 280,
    previewLoaded: false,
    embedded: false,
    revealed: !this.props.sensitive,
  };

  componentWillReceiveProps (nextProps) {
    if (!Immutable.is(this.props.card, nextProps.card)) {
      this.setState({ embedded: false, previewLoaded: false });
    }
    if (this.props.sensitive !== nextProps.sensitive) {
      this.setState({ revealed: !nextProps.sensitive });
    }
  }

  componentDidMount () {
    window.addEventListener('resize', this.handleResize, { passive: true });

    if (this.props.card && this.props.card.get('blurhash')) {
      this._decode();
    }
  }

  componentWillUnmount () {
    window.removeEventListener('resize', this.handleResize);
  }

  componentDidUpdate (prevProps) {
    const { card } = this.props;

    if (card.get('blurhash') && (!prevProps.card || prevProps.card.get('blurhash') !== card.get('blurhash')) && this.canvas) {
      this._decode();
    }
  }

  _decode () {
    if (!useBlurhash) return;

    const hash   = this.props.card.get('blurhash');
    const pixels = decode(hash, 32, 32);

    if (pixels) {
      const ctx       = this.canvas.getContext('2d');
      const imageData = new ImageData(pixels, 32, 32);

      ctx.putImageData(imageData, 0, 0);
    }
  }

  _setDimensions () {
    const width = this.node.offsetWidth;

    if (this.props.cacheWidth) {
      this.props.cacheWidth(width);
    }

    this.setState({ width });
  }

  handleResize = debounce(() => {
    if (this.node) {
      this._setDimensions();
    }
  }, 250, {
    trailing: true,
  });

  handlePhotoClick = () => {
    const { card, onOpenMedia } = this.props;

    onOpenMedia(
      Immutable.fromJS([
        {
          type: 'image',
          url: card.get('embed_url'),
          description: card.get('title'),
          meta: {
            original: {
              width: card.get('width'),
              height: card.get('height'),
            },
          },
        },
      ]),
      0,
    );
  };

  handleEmbedClick = () => {
    const { card } = this.props;

    if (card.get('type') === 'photo') {
      this.handlePhotoClick();
    } else {
      this.setState({ embedded: true });
    }
  }

  setRef = c => {
    this.node = c;

    if (this.node) {
      this._setDimensions();
    }
  }

  setCanvasRef = c => {
    this.canvas = c;
  }

  handleImageLoad = () => {
    this.setState({ previewLoaded: true });
  }

  handleReveal = e => {
    e.preventDefault();
    e.stopPropagation();
    this.setState({ revealed: true });
  }

  renderVideo () {
    const { card }  = this.props;
    const content   = { __html: addAutoPlay(card.get('html')) };
    const { width } = this.state;
    const ratio     = card.get('width') / card.get('height');
    const height    = width / ratio;

    return (

    );
  }

  render () {
    const { card, maxDescription, compact } = this.props;
    const { width, embedded, revealed } = this.state;

    if (card === null) {
      return null;
    }

    const provider    = card.get('provider_name').length === 0 ? decodeIDNA(getHostname(card.get('url'))) : card.get('provider_name');
    const horizontal  = (!compact && card.get('width') > card.get('height') && (card.get('width') + 100 >= width)) || card.get('type') !== 'link' || embedded;
    const interactive = card.get('type') !== 'link';
    const className   = classnames('status-card', { horizontal, compact, interactive });
    const title       = interactive ? {card.get('title')} : {card.get('title')};
    const ratio       = card.get('width') / card.get('height');
    const height      = (compact && !embedded) ? (width / (16 / 9)) : (width / ratio);

    const description = (

        {title}
        {!(horizontal || compact) &&
{trim(card.get('description') || '', maxDescription)}
}
        {provider}

    );

    let embed     = '';
    let canvas = <canvas width={32} height={32} ref={this.setCanvasRef} className={classnames('status-card__image-preview', { 'status-card__image-preview--hidden' : revealed && this.state.previewLoaded })} />;
    let thumbnail = ;
    let spoilerButton = (
      <button type='button' onClick={this.handleReveal} className='spoiler-button__overlay'>
        <FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' />
      </button>
    );
    spoilerButton = (

        {spoilerButton}

    );

    if (interactive) {
      if (embedded) {
        embed = this.renderVideo();
      } else {
        let iconVariant = 'play';

        if (card.get('type') === 'photo') {
          iconVariant = 'search-plus';
        }

        embed = (

            {canvas}
            {thumbnail}

            {revealed && (

                  <button onClick={this.handleEmbedClick}><Icon id={iconVariant} /></button>
                  {horizontal && <Icon id='external-link' />}

            )}
            {!revealed && spoilerButton}

        );
      }

      return (

          {embed}
          {!compact && description}

      );
    } else if (card.get('image')) {
      embed = (

          {canvas}
          {thumbnail}

      );
    } else {
      embed = (

          <Icon id='file-text' />

      );
    }

    return (

        {embed}
        {description}

    );
  }

}
<commit_msg>Fix WebUI crash on sensitive preview card with no preview thumbnail (#14261)<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import Immutable from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { FormattedMessage } from 'react-intl';
import punycode from 'punycode';
import classnames from 'classnames';
import Icon from 'mastodon/components/icon';
import { useBlurhash } from 'mastodon/initial_state';
import { decode } from 'blurhash';
import { debounce } from 'lodash';

const IDNA_PREFIX = 'xn--';

const decodeIDNA = domain => {
  return domain
    .split('.')
    .map(part => part.indexOf(IDNA_PREFIX) === 0 ? punycode.decode(part.slice(IDNA_PREFIX.length)) : part)
    .join('.');
};

const getHostname = url => {
  const parser = document.createElement('a');
  parser.href = url;
  return parser.hostname;
};

const trim = (text, len) => {
  const cut = text.indexOf(' ', len);

  if (cut === -1) {
    return text;
  }

  return text.substring(0, cut) + (text.length > len ? '…' : '');
};

const domParser = new DOMParser();

const addAutoPlay = html => {
  const document = domParser.parseFromString(html, 'text/html').documentElement;
  const iframe = document.querySelector('iframe');

  if (iframe) {
    if (iframe.src.indexOf('?') !== -1) {
      iframe.src += '&';
    } else {
      iframe.src += '?';
    }

    iframe.src += 'autoplay=1&auto_play=1';

    // DOM parser creates html/body elements around original HTML fragment,
    // so we need to get innerHTML out of the body and not the entire document
    return document.querySelector('body').innerHTML;
  }

  return html;
};

export default class Card extends React.PureComponent {

  static propTypes = {
    card: ImmutablePropTypes.map,
    maxDescription: PropTypes.number,
    onOpenMedia: PropTypes.func.isRequired,
    compact: PropTypes.bool,
    defaultWidth: PropTypes.number,
    cacheWidth: PropTypes.func,
    sensitive: PropTypes.bool,
  };

  static defaultProps = {
    maxDescription: 50,
    compact: false,
  };

  state = {
    width: this.props.defaultWidth || 280,
    previewLoaded: false,
    embedded: false,
    revealed: !this.props.sensitive,
  };

  componentWillReceiveProps (nextProps) {
    if (!Immutable.is(this.props.card, nextProps.card)) {
      this.setState({ embedded: false, previewLoaded: false });
    }
    if (this.props.sensitive !== nextProps.sensitive) {
      this.setState({ revealed: !nextProps.sensitive });
    }
  }

  componentDidMount () {
    window.addEventListener('resize', this.handleResize, { passive: true });

    if (this.props.card && this.props.card.get('blurhash') && this.canvas) {
      this._decode();
    }
  }

  componentWillUnmount () {
    window.removeEventListener('resize', this.handleResize);
  }

  componentDidUpdate (prevProps) {
    const { card } = this.props;

    if (card.get('blurhash') && (!prevProps.card || prevProps.card.get('blurhash') !== card.get('blurhash')) && this.canvas) {
      this._decode();
    }
  }

  _decode () {
    if (!useBlurhash) return;

    const hash   = this.props.card.get('blurhash');
    const pixels = decode(hash, 32, 32);

    if (pixels) {
      const ctx       = this.canvas.getContext('2d');
      const imageData = new ImageData(pixels, 32, 32);

      ctx.putImageData(imageData, 0, 0);
    }
  }

  _setDimensions () {
    const width = this.node.offsetWidth;

    if (this.props.cacheWidth) {
      this.props.cacheWidth(width);
    }

    this.setState({ width });
  }

  handleResize = debounce(() => {
    if (this.node) {
      this._setDimensions();
    }
  }, 250, {
    trailing: true,
  });

  handlePhotoClick = () => {
    const { card, onOpenMedia } = this.props;

    onOpenMedia(
      Immutable.fromJS([
        {
          type: 'image',
          url: card.get('embed_url'),
          description: card.get('title'),
          meta: {
            original: {
              width: card.get('width'),
              height: card.get('height'),
            },
          },
        },
      ]),
      0,
    );
  };

  handleEmbedClick = () => {
    const { card } = this.props;

    if (card.get('type') === 'photo') {
      this.handlePhotoClick();
    } else {
      this.setState({ embedded: true });
    }
  }

  setRef = c => {
    this.node = c;

    if (this.node) {
      this._setDimensions();
    }
  }

  setCanvasRef = c => {
    this.canvas = c;
  }

  handleImageLoad = () => {
    this.setState({ previewLoaded: true });
  }

  handleReveal = e => {
    e.preventDefault();
    e.stopPropagation();
    this.setState({ revealed: true });
  }

  renderVideo () {
    const { card }  = this.props;
    const content   = { __html: addAutoPlay(card.get('html')) };
    const { width } = this.state;
    const ratio     = card.get('width') / card.get('height');
    const height    = width / ratio;

    return (

    );
  }

  render () {
    const { card, maxDescription, compact } = this.props;
    const { width, embedded, revealed } = this.state;

    if (card === null) {
      return null;
    }

    const provider    = card.get('provider_name').length === 0 ? decodeIDNA(getHostname(card.get('url'))) : card.get('provider_name');
    const horizontal  = (!compact && card.get('width') > card.get('height') && (card.get('width') + 100 >= width)) || card.get('type') !== 'link' || embedded;
    const interactive = card.get('type') !== 'link';
    const className   = classnames('status-card', { horizontal, compact, interactive });
    const title       = interactive ? {card.get('title')} : {card.get('title')};
    const ratio       = card.get('width') / card.get('height');
    const height      = (compact && !embedded) ? (width / (16 / 9)) : (width / ratio);

    const description = (

        {title}
        {!(horizontal || compact) &&
{trim(card.get('description') || '', maxDescription)}
}
        {provider}

    );

    let embed     = '';
    let canvas = <canvas width={32} height={32} ref={this.setCanvasRef} className={classnames('status-card__image-preview', { 'status-card__image-preview--hidden' : revealed && this.state.previewLoaded })} />;
    let thumbnail = ;
    let spoilerButton = (
      <button type='button' onClick={this.handleReveal} className='spoiler-button__overlay'>
        <FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' />
      </button>
    );
    spoilerButton = (

        {spoilerButton}

    );

    if (interactive) {
      if (embedded) {
        embed = this.renderVideo();
      } else {
        let iconVariant = 'play';

        if (card.get('type') === 'photo') {
          iconVariant = 'search-plus';
        }

        embed = (

            {canvas}
            {thumbnail}

            {revealed && (

                  <button onClick={this.handleEmbedClick}><Icon id={iconVariant} /></button>
                  {horizontal && <Icon id='external-link' />}

            )}
            {!revealed && spoilerButton}

        );
      }

      return (

          {embed}
          {!compact && description}

      );
    } else if (card.get('image')) {
      embed = (

          {canvas}
          {thumbnail}

      );
    } else {
      embed = (

          <Icon id='file-text' />

      );
    }

    return (

        {embed}
        {description}

    );
  }

}
<|endoftext|>


<commit_before>jest.autoMockOff();

const babel = require('babel-core');

function transform(code) {
  return babel.transform(code,  {
    whitelist: [],
    plugins: [require('../index')],
  }).code;
}

function unpad(str) {
  const lines = str.split('\n');
  const m = lines[1] && lines[1].match(/^\s+/);
  if (!m) {
    return str;
  }
  const spaces = m[0].length;
  return lines.map(
    line => line.slice(spaces)
  ).join('\n').trim();
}

describe('simplify-plugin', () => {
  it('should turn undefined into void 0', () => {
    const expected = 'void 0;';
    const source = 'undefined;';
    expect(transform(source)).toBe(expected);
  });

  it('should strip unnecessary property literal qoutes', () => {
    const source = `var x = { 'foo': 'bar' };`;
    const expected = `var x = { foo: 'bar' };`;
    expect(transform(source)).toBe(expected);
  });

  it('should turn subscript into dot', () => {
    const source = `foo['bar'];`;
    const expected = `foo.bar;`;
    expect(transform(source)).toBe(expected);
  });

  it('should turn Number(x) to +x', () => {
    const source = `Number(x);`;
    const expected = `+x;`;
    expect(transform(source)).toBe(expected);
  });

  it(`should turn String(x) to x + ''`, () => {
    const source = `String(x);`;
    const expected = `x + "";`;
    expect(transform(source)).toBe(expected);
  });

  it(`!foo && bar -> foo || bar`, () => {
    const source = `!foo && bar;`;
    const expected = `foo || bar;`;
    expect(transform(source)).toBe(expected);
  });

  it('should shorten bool', () => {
    const source = `true; false;`;
    const expected = `!0;!1;`;
    expect(transform(source)).toBe(expected);
  });

  it('should put values first in binary expressions', () => {
    const source = `a === 1;`;
    const expected = `1 === a;`;
    expect(transform(source)).toBe(expected);
  });

  it('should simplify comparison', () => {
    const source = `'function' === typeof a;`;
    const expected = `'function' == typeof a;`;
    expect(transform(source)).toBe(expected);
  });

  it('should flip conditionals', () => {
    const source = `!foo ? 'foo' : 'bar';`;
    const expected = `foo ? 'bar' : 'foo';`;
    expect(transform(source)).toBe(expected);
  });

  it('concat vars in for loops', () => {
    const source = unpad(`
      var i = 0;
      var j = 0;
      for (var x = 0; x < 10; x++) console.log(i + x);
    `);
    const expected = 'for (var i = 0, j = 0, x = 0; x < 10; x++) console.log(i + x);';

    expect(transform(source).trim()).toBe(expected);
  });

  it('concat vars', () => {
    const source = unpad(`
      var i = 0;
      var x = 0;
      var y = 0;
    `);
    const expected = unpad(`
      var i = 0,
          x = 0,
          y = 0;
    `);

    expect(transform(source)).toBe(expected);
  });

  it('should turn for loop block to a single statement', () => {
    const source = unpad(`
      for (var x = 0; x < 10; x++) {
        console.log(x);
      }
    `);
    const expected = 'for (var x = 0; x < 10; x++) console.log(x);';

    expect(transform(source).trim()).toBe(expected);
  });

  it('should turn block statements to sequence expr', () => {
    const source = unpad(`
      for (var x = 0; x < 10; x++) {
        console.log(x);
        console.log(x);
      }
    `);
    const expected =
      'for (var x = 0; x < 10; x++) console.log(x), console.log(x);';

    expect(transform(source).trim()).toBe(expected);
  });

  it('should turn blocks and seqeunce but maintain vars', () => {
    const source = unpad(`
      function foo() {
        wow();
        for (var x = 0; x < 10; x++) {
          var z = bar;
          z();
        }
      }
    `);
    const expected = unpad(`
      function foo() {
        wow();

        for (var z, x = 0; x < 10; x++) z = bar, z();
      }
    `);

    expect(transform(source).trim()).toBe(expected);
  });

  it('should turn if to gaurded expression', () => {
    const source = unpad(`
      function foo() {
        if (x) a();
      }
    `);
    const expected = unpad(`
      function foo() {
        x && a();
      }
    `);

    expect(transform(source).trim()).toBe(expected);
  });

  it('should turn if statement to conditional', () => {
    const source = unpad(`
      function foo() {
        if (x) a();
        else b();
      }
    `);
    const expected = unpad(`
      function foo() {
        x ? a() : b();
      }
    `);

    expect(transform(source).trim()).toBe(expected);
  });

  it('should turn this into a conditional', () => {
    const source = unpad(`
      function foo(a) {
        if (a && a.b != null) {
          if ((a.c--) === 1) {
            delete a.c;
          }
          return a.b;
        }
        return bar(a);
      }
    `);
    const expected = unpad(`
      function foo(a) {
        return a && null != a.b ? (1 == a.c-- && delete a.c, a.b) : bar(a);
      }
    `);

    expect(transform(source)).toBe(expected);
  });

  it('should turn this into a conditional', () => {
    const source = unpad(`
      function foo(a) {
        if (a && a.b != null) {
          if ((a.c--) === 1) {
            delete a.c;
          }
          return a.b;
        }
        return bar(a);
      }
    `);
    const expected = unpad(`
      function foo(a) {
        return a && null != a.b ? (1 == a.c-- && delete a.c, a.b) : bar(a);
      }
    `);

    expect(transform(source)).toBe(expected);
  });

  it('should turn this into a conditional', () => {
    const source = unpad(`
      function foo(a) {
        if (a) {
          return a.b;
        } else {
          return bar(a);
        }
      }
    `);
    const expected = unpad(`
      function foo(a) {
        return a ? a.b : bar(a);
      }
    `);

    expect(transform(source)).toBe(expected);
  });

  it('should turn IIFE to negation', () => {
    const source = unpad(`
      (function() {
        x();
      })();
      y = (function() {
        x();
      })();
    `);
    const expected = unpad(`
      !function () {
        x();
      }();
      y = (function () {
        x();
      })();
    `);

    expect(transform(source)).toBe(expected);
  });

  it('should remove the else block if early return', () => {
    const source = unpad(`
    function foo() {
      if (1) {
        return 2;
      } else {
        lol(1);
        lol(2);
      }
    }
    `);

    const expected = unpad(`
      function foo() {
        if (1) return 2;
        lol(1), lol(2);
      }
    `);

    expect(transform(source)).toBe(expected);
  });

  it('should merge blocks into a return with sequence expr', () => {
    const source = unpad(`
      function foo() {
        y();
        x();
        return 1;
      }
    `);

    // TODO shouldn't print those parens (yuck).
    const expected = unpad(`
      function foo() {
        return (y(), x(), 1);
      }
    `);

    expect(transform(source)).toBe(expected);
  });

  it('should merge blocks into a return with sequence expr', () => {
    const source = unpad(`
      function foo() {
        try {
          x();
        } catch (e) {
          1;
        }
        y();
        return 1;
      }
    `);

    // TODO shouldn't print those parens (yuck).
    const expected = unpad(`
      function foo() {
        try {
          x();
        } catch (e) {
          1;
        }
        return (y(), 1);
      }
    `);

    expect(transform(source)).toBe(expected);
  });

  it('should merge expressions into the init part of for', () => {
    const source = unpad(`
      function foo() {
        x();
        y();
        for (; i < 10; i++) z();
      }
    `);

    const expected = unpad(`
      function foo() {
        for (x(), y(); i < 10; i++) z();
      }
    `);

    expect(transform(source).trim()).toBe(expected);
  });

  it('should merge statements into the init part of for even when there are others', () => {
    const source = unpad(`
      function foo() {
        x();
        y();
        for (z(); i < 10; i++) z();
      }
    `);

    const expected = unpad(`
      function foo() {
        for (x(), y(), z(); i < 10; i++) z();
      }
    `);

    expect(transform(source).trim()).toBe(expected);
  });

  xit('should remove empty returns', () => {
     const source = unpad(`
       function foo() {
         lol();
         return;
       }
     `);

     const expected = unpad(`
       function foo() {
         lol();
       }
     `);

    expect(transform(source)).toBe(expected);
  });

  it('should merge if statements with following expressions using void', () => {
     const source = unpad(`
       function foo() {
         if(window.self != window.top) {
           if(__DEV__) {
             console.log('lol', name);
           }
           return;
         }
         lol();
       }
     `);

     const expected = unpad(`
       function foo() {
         return window.self == window.top ? void lol() : void (__DEV__ && console.log('lol', name));
       }
     `);

    expect(transform(source)).toBe(expected);
  });

  it('should not try to merge `if` when there are multiple statements to follow', () => {
     const source = unpad(`
       function foo() {
         if(window.self != window.top) {
           if(__DEV__) {
             console.log('lol', name);
           }
           return;
         }
         lol();
         try { lol() } catch (e) {}
       }
     `);

     const expected = unpad(`
       function foo() {
         if (window.self != window.top) return void (__DEV__ && console.log('lol', name));
         lol();

         try {
           lol();
         } catch (e) {}
       }
     `);

    expect(transform(source)).toBe(expected);
  });

  it('should handle missing return arg when merging if statements', () => {
    const source = unpad(`
      function foo() {
        if (a) {
          return;
        }

        b = true;
      }
    `);

    const expected = unpad(`
      function foo() {
        return a || void (b = !0);
      }
    `);

    expect(transform(source)).toBe(expected);
  });

  it('should handle returns with no args', () => {
    const source = unpad(`
      function foo(a) {
        if (a && a.b != null) {
          if ((a.c--) === 1) {
            return;
          }
          return a.b;
        }
        return bar(a);
      }
    `);
    const expected = unpad(`
      function foo(a) {
        return a && null != a.b ? 1 == a.c-- ? void 0 : a.b : bar(a);
      }
    `);

    expect(transform(source)).toBe(expected);
  });

  it('should convert whiles to fors', () => {
    const source = unpad(`
      function foo(a) {
        while(true) {
          bar();
        }
      }
    `);
    const expected = unpad(`
      function foo(a) {
        for (; !0;) bar();
      }
    `);

    expect(transform(source)).toBe(expected);
  });

  it('should convert whiles to fors and merge vars', () => {
    const source = unpad(`
      function foo(a) {
        var bar = baz;
        while(true) {
          bar();
        }
      }
    `);
    const expected = unpad(`
      function foo(a) {
        for (var bar = baz; !0;) bar();
      }
    `);

    expect(transform(source)).toBe(expected);
  });

  it('should combine returns', () => {
    const source = unpad(`
      function foo() {
        if (a) {
          if (a.b) {
            if(a.b.c) {
              if(a.b.c()){
                return;
              }
            }
          }
        }
        for (; true;) wat();
      }
   `);

    // Extra parens babel bug #2602
    const expected = unpad(`
      function foo() {
        if (!(a && (a.b && (a.b.c && a.b.c())))) for (; !0;) wat();
      }
   `);

    expect(transform(source)).toBe(expected);
  });

  it('should convert early returns to negated if blocks', () => {
    const source = unpad(`
      function foo(a) {
        if (lol) return;
        doThings();
        doOtherThings();
      }
      function bar(a) {
        if (lol) {
          return;
        }
        try {
          doThings();
        } catch (e) {
          doOtherThings();
        }
      }
      function baz() {
        while (wow) if (lol) return;
        boo();
      }
    `);
    const expected = unpad(`
      function foo(a) {
        return lol || void (doThings(), doOtherThings());
      }
      function bar(a) {
        if (!lol) try {
          doThings();
        } catch (e) {
          doOtherThings();
        }
      }
      function baz() {
        for (; wow;) if (lol) return;

        boo();
      }

    `);
    expect(transform(source)).toBe(expected);
  });

  it('should merge function blocks into sequence expressions', () => {
    const source = unpad(`
      function foo() {
        a();
        var x = bar();
        b(x);
        this.d = x;
      }
      function bar() {
        x();
        while (x) {
          if (x) x();
        }
        try { y(); } catch (e) {}
        var z = x();
        z();
        while (a) b();
        c();
        z();
      }
    `);

    // TODO merge var z into the init part of for.
    const expected = unpad(`
      function foo() {
        a();

        var x = bar();
        b(x), this.d = x;
      }
      function bar() {
        for (x(); x;) x && x();

        try {
          y();
        } catch (e) {}
        var z = x();

        for (z(); a;) b();

        c(), z();
      }
    `);

    expect(transform(source)).toBe(expected);
  });

  it('should merge expressions into if statements test', () => {

  });

  it('should understand continue statements', () => {

  });
});
<commit_msg>remove old test case<commit_after>jest.autoMockOff();

const babel = require('babel-core');

function transform(code) {
  return babel.transform(code,  {
    whitelist: [],
    plugins: [require('../index')],
  }).code;
}

function unpad(str) {
  const lines = str.split('\n');
  const m = lines[1] && lines[1].match(/^\s+/);
  if (!m) {
    return str;
  }
  const spaces = m[0].length;
  return lines.map(
    line => line.slice(spaces)
  ).join('\n').trim();
}

describe('simplify-plugin', () => {
  it('should turn undefined into void 0', () => {
    const expected = 'void 0;';
    const source = 'undefined;';
    expect(transform(source)).toBe(expected);
  });

  it('should strip unnecessary property literal qoutes', () => {
    const source = `var x = { 'foo': 'bar' };`;
    const expected = `var x = { foo: 'bar' };`;
    expect(transform(source)).toBe(expected);
  });

  it('should turn subscript into dot', () => {
    const source = `foo['bar'];`;
    const expected = `foo.bar;`;
    expect(transform(source)).toBe(expected);
  });

  it('should turn Number(x) to +x', () => {
    const source = `Number(x);`;
    const expected = `+x;`;
    expect(transform(source)).toBe(expected);
  });

  it(`should turn String(x) to x + ''`, () => {
    const source = `String(x);`;
    const expected = `x + "";`;
    expect(transform(source)).toBe(expected);
  });

  it(`!foo && bar -> foo || bar`, () => {
    const source = `!foo && bar;`;
    const expected = `foo || bar;`;
    expect(transform(source)).toBe(expected);
  });

  it('should shorten bool', () => {
    const source = `true; false;`;
    const expected = `!0;!1;`;
    expect(transform(source)).toBe(expected);
  });

  it('should put values first in binary expressions', () => {
    const source = `a === 1;`;
    const expected = `1 === a;`;
    expect(transform(source)).toBe(expected);
  });

  it('should simplify comparison', () => {
    const source = `'function' === typeof a;`;
    const expected = `'function' == typeof a;`;
    expect(transform(source)).toBe(expected);
  });

  it('should flip conditionals', () => {
    const source = `!foo ? 'foo' : 'bar';`;
    const expected = `foo ? 'bar' : 'foo';`;
    expect(transform(source)).toBe(expected);
  });

  it('concat vars in for loops', () => {
    const source = unpad(`
      var i = 0;
      var j = 0;
      for (var x = 0; x < 10; x++) console.log(i + x);
    `);
    const expected = 'for (var i = 0, j = 0, x = 0; x < 10; x++) console.log(i + x);';

    expect(transform(source).trim()).toBe(expected);
  });

  it('concat vars', () => {
    const source = unpad(`
      var i = 0;
      var x = 0;
      var y = 0;
    `);
    const expected = unpad(`
      var i = 0,
          x = 0,
          y = 0;
    `);

    expect(transform(source)).toBe(expected);
  });

  it('should turn for loop block to a single statement', () => {
    const source = unpad(`
      for (var x = 0; x < 10; x++) {
        console.log(x);
      }
    `);
    const expected = 'for (var x = 0; x < 10; x++) console.log(x);';

    expect(transform(source).trim()).toBe(expected);
  });

  it('should turn block statements to sequence expr', () => {
    const source = unpad(`
      for (var x = 0; x < 10; x++) {
        console.log(x);
        console.log(x);
      }
    `);
    const expected =
      'for (var x = 0; x < 10; x++) console.log(x), console.log(x);';

    expect(transform(source).trim()).toBe(expected);
  });

  it('should turn if to gaurded expression', () => {
    const source = unpad(`
      function foo() {
        if (x) a();
      }
    `);
    const expected = unpad(`
      function foo() {
        x && a();
      }
    `);

    expect(transform(source).trim()).toBe(expected);
  });

  it('should turn if statement to conditional', () => {
    const source = unpad(`
      function foo() {
        if (x) a();
        else b();
      }
    `);
    const expected = unpad(`
      function foo() {
        x ? a() : b();
      }
    `);

    expect(transform(source).trim()).toBe(expected);
  });

  it('should turn this into a conditional', () => {
    const source = unpad(`
      function foo(a) {
        if (a && a.b != null) {
          if ((a.c--) === 1) {
            delete a.c;
          }
          return a.b;
        }
        return bar(a);
      }
    `);
    const expected = unpad(`
      function foo(a) {
        return a && null != a.b ? (1 == a.c-- && delete a.c, a.b) : bar(a);
      }
    `);

    expect(transform(source)).toBe(expected);
  });

  it('should turn this into a conditional', () => {
    const source = unpad(`
      function foo(a) {
        if (a && a.b != null) {
          if ((a.c--) === 1) {
            delete a.c;
          }
          return a.b;
        }
        return bar(a);
      }
    `);
    const expected = unpad(`
      function foo(a) {
        return a && null != a.b ? (1 == a.c-- && delete a.c, a.b) : bar(a);
      }
    `);

    expect(transform(source)).toBe(expected);
  });

  it('should turn this into a conditional', () => {
    const source = unpad(`
      function foo(a) {
        if (a) {
          return a.b;
        } else {
          return bar(a);
        }
      }
    `);
    const expected = unpad(`
      function foo(a) {
        return a ? a.b : bar(a);
      }
    `);

    expect(transform(source)).toBe(expected);
  });

  it('should turn IIFE to negation', () => {
    const source = unpad(`
      (function() {
        x();
      })();
      y = (function() {
        x();
      })();
    `);
    const expected = unpad(`
      !function () {
        x();
      }();
      y = (function () {
        x();
      })();
    `);

    expect(transform(source)).toBe(expected);
  });

  it('should remove the else block if early return', () => {
    const source = unpad(`
    function foo() {
      if (1) {
        return 2;
      } else {
        lol(1);
        lol(2);
      }
    }
    `);

    const expected = unpad(`
      function foo() {
        if (1) return 2;
        lol(1), lol(2);
      }
    `);

    expect(transform(source)).toBe(expected);
  });

  it('should merge blocks into a return with sequence expr', () => {
    const source = unpad(`
      function foo() {
        y();
        x();
        return 1;
      }
    `);

    // TODO shouldn't print those parens (yuck).
    const expected = unpad(`
      function foo() {
        return (y(), x(), 1);
      }
    `);

    expect(transform(source)).toBe(expected);
  });

  it('should merge blocks into a return with sequence expr', () => {
    const source = unpad(`
      function foo() {
        try {
          x();
        } catch (e) {
          1;
        }
        y();
        return 1;
      }
    `);

    // TODO shouldn't print those parens (yuck).
    const expected = unpad(`
      function foo() {
        try {
          x();
        } catch (e) {
          1;
        }
        return (y(), 1);
      }
    `);

    expect(transform(source)).toBe(expected);
  });

  it('should merge expressions into the init part of for', () => {
    const source = unpad(`
      function foo() {
        x();
        y();
        for (; i < 10; i++) z();
      }
    `);

    const expected = unpad(`
      function foo() {
        for (x(), y(); i < 10; i++) z();
      }
    `);

    expect(transform(source).trim()).toBe(expected);
  });

  it('should merge statements into the init part of for even when there are others', () => {
    const source = unpad(`
      function foo() {
        x();
        y();
        for (z(); i < 10; i++) z();
      }
    `);

    const expected = unpad(`
      function foo() {
        for (x(), y(), z(); i < 10; i++) z();
      }
    `);

    expect(transform(source).trim()).toBe(expected);
  });

  xit('should remove empty returns', () => {
     const source = unpad(`
       function foo() {
         lol();
         return;
       }
     `);

     const expected = unpad(`
       function foo() {
         lol();
       }
     `);

    expect(transform(source)).toBe(expected);
  });

  it('should merge if statements with following expressions using void', () => {
     const source = unpad(`
       function foo() {
         if(window.self != window.top) {
           if(__DEV__) {
             console.log('lol', name);
           }
           return;
         }
         lol();
       }
     `);

     const expected = unpad(`
       function foo() {
         return window.self == window.top ? void lol() : void (__DEV__ && console.log('lol', name));
       }
     `);

    expect(transform(source)).toBe(expected);
  });

  it('should not try to merge `if` when there are multiple statements to follow', () => {
     const source = unpad(`
       function foo() {
         if(window.self != window.top) {
           if(__DEV__) {
             console.log('lol', name);
           }
           return;
         }
         lol();
         try { lol() } catch (e) {}
       }
     `);

     const expected = unpad(`
       function foo() {
         if (window.self != window.top) return void (__DEV__ && console.log('lol', name));
         lol();

         try {
           lol();
         } catch (e) {}
       }
     `);

    expect(transform(source)).toBe(expected);
  });

  it('should handle missing return arg when merging if statements', () => {
    const source = unpad(`
      function foo() {
        if (a) {
          return;
        }

        b = true;
      }
    `);

    const expected = unpad(`
      function foo() {
        return a || void (b = !0);
      }
    `);

    expect(transform(source)).toBe(expected);
  });

  it('should handle returns with no args', () => {
    const source = unpad(`
      function foo(a) {
        if (a && a.b != null) {
          if ((a.c--) === 1) {
            return;
          }
          return a.b;
        }
        return bar(a);
      }
    `);
    const expected = unpad(`
      function foo(a) {
        return a && null != a.b ? 1 == a.c-- ? void 0 : a.b : bar(a);
      }
    `);

    expect(transform(source)).toBe(expected);
  });

  it('should convert whiles to fors', () => {
    const source = unpad(`
      function foo(a) {
        while(true) {
          bar();
        }
      }
    `);
    const expected = unpad(`
      function foo(a) {
        for (; !0;) bar();
      }
    `);

    expect(transform(source)).toBe(expected);
  });

  it('should convert whiles to fors and merge vars', () => {
    const source = unpad(`
      function foo(a) {
        var bar = baz;
        while(true) {
          bar();
        }
      }
    `);
    const expected = unpad(`
      function foo(a) {
        for (var bar = baz; !0;) bar();
      }
    `);

    expect(transform(source)).toBe(expected);
  });

  it('should combine returns', () => {
    const source = unpad(`
      function foo() {
        if (a) {
          if (a.b) {
            if(a.b.c) {
              if(a.b.c()){
                return;
              }
            }
          }
        }
        for (; true;) wat();
      }
   `);

    // Extra parens babel bug #2602
    const expected = unpad(`
      function foo() {
        if (!(a && (a.b && (a.b.c && a.b.c())))) for (; !0;) wat();
      }
   `);

    expect(transform(source)).toBe(expected);
  });

  it('should convert early returns to negated if blocks', () => {
    const source = unpad(`
      function foo(a) {
        if (lol) return;
        doThings();
        doOtherThings();
      }
      function bar(a) {
        if (lol) {
          return;
        }
        try {
          doThings();
        } catch (e) {
          doOtherThings();
        }
      }
      function baz() {
        while (wow) if (lol) return;
        boo();
      }
    `);
    const expected = unpad(`
      function foo(a) {
        return lol || void (doThings(), doOtherThings());
      }
      function bar(a) {
        if (!lol) try {
          doThings();
        } catch (e) {
          doOtherThings();
        }
      }
      function baz() {
        for (; wow;) if (lol) return;

        boo();
      }

    `);
    expect(transform(source)).toBe(expected);
  });

  it('should merge function blocks into sequence expressions', () => {
    const source = unpad(`
      function foo() {
        a();
        var x = bar();
        b(x);
        this.d = x;
      }
      function bar() {
        x();
        while (x) {
          if (x) x();
        }
        try { y(); } catch (e) {}
        var z = x();
        z();
        while (a) b();
        c();
        z();
      }
    `);

    // TODO merge var z into the init part of for.
    const expected = unpad(`
      function foo() {
        a();

        var x = bar();
        b(x), this.d = x;
      }
      function bar() {
        for (x(); x;) x && x();

        try {
          y();
        } catch (e) {}
        var z = x();

        for (z(); a;) b();

        c(), z();
      }
    `);

    expect(transform(source)).toBe(expected);
  });

  it('should merge expressions into if statements test', () => {

  });

  it('should understand continue statements', () => {

  });
});
<|endoftext|>


<commit_before>/**
 * Created by lja on 2015/11/20.
 */

var type = (function () {
    'use strict';

    var class2type = {},
        toString = class2type.toString;

    ('Boolean Number String Function Array Date RegExp Object Error').split(' ').forEach(function (name) {
        class2type['[object ' + name + ']'] = name.toLowerCase();
    });

    function isWindow(obj) {
        return obj !== null && obj.window && obj === obj.window;
    }

    function isFunction(obj) {
        return class2type[toString.call(obj)] === 'function';
    }

    function isDocument(obj) {
        return obj && obj.nodeType === obj.DOCUMENT_NODE;
    }

    function isObject(obj) {
        return class2type[toString.call(obj)] === 'object';
    }

    function isArray(obj) {
        return class2type[toString.call(obj)] === 'array';
    }

    function isPlainObject(obj) {
        return isObject(obj) && !isWindow(obj) &&
            Object.getPrototypeOf(obj) === Object.prototype;
    }

    return {
        isWindow: isWindow,
        isFunction: isFunction,
        isDocument: isDocument,
        isObject: isObject,
        isPlainObject: isPlainObject,
        isArray: isArray
    };
})();

var Jackey8 = (function (type) {
    'use strict';

    var J8,
        emptyArray = [],
        jackey8 = {},
        table = document.createElement('table'),
        tableRow = document.createElement('tr'),
        containers = {
            'tr': document.createElement('tbody'),
            'tbody': table, 'thead': table, 'tfoot': table,
            'td': tableRow, 'th': tableRow,
            '*': document.createElement('div')
        },
    // special attributes that should be get/set via method calls
        methodAttributes = ['val', 'css', 'html', 'text', 'data', 'width', 'height', 'offset'],
        simpleSelectorRE = /^[\w-]*$/,//字母 数字 或者下划线，不包括空格
        tagExpanderRE = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,//tag reg
        readyRE = /complete|loaded|interactive/,
        htmlFragmentRE = /^\s*<(\w+|!)[^>]*>/;//html片段

    function filterNullArray(array) {
        return emptyArray.filter.call(array, function (item) {
            return item !== null;
        });
    }

    //decorate the __proto__ and selector
    //todo: __proto__ not supported on IE
    jackey8.decorateDom = function (dom, selector) {
        dom = dom || [];
        dom.__proto__ = J8.fn;//jshint ignore:line
        dom.selector = selector;
        return dom;
    };

    jackey8.createNodeByHtmlFragment = function (html, name, properties) {
        var dom, nodes, container;
        if (simpleSelectorRE.test(html)) {
            dom = J8(document.createElement(RegExp.$1));
        }

        if (!dom) {
            if (html.replace) {
                html = html.replace(tagExpanderRE, '<$1></$2>');
            }

            if (name === void 0) {
                name = htmlFragmentRE.test(html) && RegExp.$1;
            }

            if (!(name in containers)) {
                name = '*';
            }

            container = containers[name];
            container.innerHTML = '' + html;//add html fragment to dom, make it node
            dom = J8.each(emptyArray.slice.call(container.childNodes), function () {
                container.removeChild(this);
            });
        }

        if (type.isPlainObject(properties)) {
            nodes = J8(dom);
            J8.each(properties, function (key, value) {
                if (methodAttributes.indexOf(key) > -1) {
                    nodes[key](value);
                } else {
                    nodes.attr(key, value);
                }
            });
        }
    };

    /**
     * find selector in context
     * by id class and 复杂类型：ul li
     * @param context
     * @param selector
     */
    jackey8.queryDom = function (context, selector) {
        var found = [],
            maybeId = selector[0] === '#',
            maybeClass = selector[0] === '.',
            name = maybeId || maybeClass ? selector.slice(1) : selector,
            isSample = simpleSelectorRE.test(name);

        if (!type.isDocument(context)) {
            return found;
        }

        if (!isSample) {
            found = emptyArray.slice.call(context.querySelectorAll(selector));
        } else {
            if (maybeId) {
                found = context.getElementById(name);
                if (found) {
                    return [found];
                }
                return [];
            }

            if (maybeClass) {
                found = context.getElementsByClassName(name);
            } else {
                found = context.getElementsByTagName(name);
            }
        }

        return emptyArray.slice.call(found);
    };

    //selector: empty string function object array and the instance of Jackey8
    jackey8.init = function (selector, context) {
        var dom;

        //if nothing given, return an empty collection
        if (!selector) {
            return jackey8.decorateDom();
        }

        if (typeof selector === 'string') {
            selector = selector.trim();

            //如果它是一个html片段，则创建一个节点
            //提示：在chrome21和Firefox15下面，
            // 如果不是<开头， 会抛错
            if (selector[0] === '<' && htmlFragmentRE.test(selector)) {
                //todo:
                dom = jackey8.createNodeByHtmlFragment(selector, RegExp.$1, context);
                selector = null;
            }
            //如果有parent，则先找到parent,然后使用find去找到selector
            else if (context !== void 0) {
                J8(context).find(selector);
            }
            else {
                // 正常的选取
                // 1 id 2 class 3 复杂选取
                // 作用域为document，因为如果存在context，则执行$(conetxt).find(selector)
                dom = jackey8.queryDom(document, selector);
            }
        } else if (type.isFunction(selector)) {
            J8(document).ready(selector);
        } else if (selector instanceof jackey8.decorateDom) {
            return selector;
        } else if (type.isArray(selector)) {
            dom = filterNullArray(selector);
        } else if (type.isObject(selector)) {
            dom = [selector];
            selector = null;
        } else {
            dom = [];
        }

        return jackey8.decorateDom(dom, selector);
    };

    J8 = function (selector, context) {
        return jackey8.init(selector, context);
    };

    J8.each = function (elements, callback) {
        var i, key;
        if (type.isArray(elements)) {
            for (i = 0; i < elements.length; i++) {
                if (callback.call(elements[i], i, elements[i]) === false) {
                    return elements;
                }
            }
        } else {
            for (key in elements) {
                if (callback.call(elements[key], key, elements[key]) === false) {
                    return elements;
                }
            }
        }
        return elements;
    }

    J8.fn = {
        forEach: emptyArray.forEach,
        reduce: emptyArray.reduce,
        push: emptyArray.push,
        sort: emptyArray.sort,
        indexOf: emptyArray.indexOf,
        concat: emptyArray.concat,
        ready: function (callback) {
            if (readyRE.test(document.readyState) && document.body) {
                callback(J8);
            } else {
                //todo: need to test DOMContentLoaded in each browsers
                document.addEventListener('DOMContentLoaded', function () {
                    callback(J8);
                }, false);
                return this;
            }
        }
    };

    return J8;

})(type);

window.Jackey8 = Jackey8;

if (window.J8 === void 0) {
    window.J8 = Jackey8;
}
<commit_msg>add up<commit_after>/**
 * Created by lja on 2015/11/20.
 */

var type = (function () {
    'use strict';

    var class2type = {},
        toString = class2type.toString;

    ('Boolean Number String Function Array Date RegExp Object Error').split(' ').forEach(function (name) {
        class2type['[object ' + name + ']'] = name.toLowerCase();
    });

    function isWindow(obj) {
        return obj !== null && obj.window && obj === obj.window;
    }

    function isFunction(obj) {
        return class2type[toString.call(obj)] === 'function';
    }

    function isDocument(obj) {
        return obj && obj.nodeType === obj.DOCUMENT_NODE;
    }

    function isObject(obj) {
        return class2type[toString.call(obj)] === 'object';
    }

    function isArray(obj) {
        return class2type[toString.call(obj)] === 'array';
    }

    function isPlainObject(obj) {
        return isObject(obj) && !isWindow(obj) &&
            Object.getPrototypeOf(obj) === Object.prototype;
    }

    return {
        isWindow: isWindow,
        isFunction: isFunction,
        isDocument: isDocument,
        isObject: isObject,
        isPlainObject: isPlainObject,
        isArray: isArray
    };
})();

var Jackey8 = (function (type) {
    'use strict';

    var J8,
        emptyArray = [],
        jackey8 = {},
        table = document.createElement('table'),
        tableRow = document.createElement('tr'),
        containers = {
            'tr': document.createElement('tbody'),
            'tbody': table, 'thead': table, 'tfoot': table,
            'td': tableRow, 'th': tableRow,
            '*': document.createElement('div')
        },
    // special attributes that should be get/set via method calls
        methodAttributes = ['val', 'css', 'html', 'text', 'data', 'width', 'height', 'offset'],
        simpleSelectorRE = /^[\w-]*$/,//字母 数字 或者下划线，不包括空格
        tagExpanderRE = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,//tag reg
        readyRE = /complete|loaded|interactive/,
        htmlFragmentRE = /^\s*<(\w+|!)[^>]*>/;//html片段

    function filterNullArray(array) {
        return emptyArray.filter.call(array, function (item) {
            return item !== null;
        });
    }

    //decorate the __proto__ and selector
    //todo: __proto__ not supported on IE
    jackey8.decorateDom = function (dom, selector) {
        dom = dom || [];
        dom.__proto__ = J8.fn;//jshint ignore:line
        dom.selector = selector;
        return dom;
    };

    jackey8.createNodeByHtmlFragment = function (html, name, properties) {
        var dom, nodes, container;
        if (simpleSelectorRE.test(html)) {
            dom = J8(document.createElement(RegExp.$1));
        }

        if (!dom) {
            if (html.replace) {
                html = html.replace(tagExpanderRE, '<$1></$2>');
            }

            if (name === void 0) {
                name = htmlFragmentRE.test(html) && RegExp.$1;
            }

            if (!(name in containers)) {
                name = '*';
            }

            container = containers[name];
            container.innerHTML = '' + html;//add html fragment to dom, make it node
            dom = J8.each(emptyArray.slice.call(container.childNodes), function () {
                container.removeChild(this);
            });
        }

        if (type.isPlainObject(properties)) {
            nodes = J8(dom);
            J8.each(properties, function (key, value) {
                if (methodAttributes.indexOf(key) > -1) {
                    nodes[key](value);
                } else {
                    nodes.attr(key, value);
                }
            });
        }
    };

    /**
     * find selector in context
     * by id class and 复杂类型：ul li
     * @param context
     * @param selector
     */
    jackey8.queryDom = function (context, selector) {
        var found = [],
            maybeId = selector[0] === '#',
            maybeClass = selector[0] === '.',
            name = maybeId || maybeClass ? selector.slice(1) : selector,
            isSample = simpleSelectorRE.test(name);

        if (!type.isDocument(context)) {
            return found;
        }

        if (!isSample) {
            found = emptyArray.slice.call(context.querySelectorAll(selector));
        } else {
            if (maybeId) {
                found = context.getElementById(name);
                if (found) {
                    return [found];
                }
                return [];
            }

            if (maybeClass) {
                found = context.getElementsByClassName(name);
            } else {
                found = context.getElementsByTagName(name);
            }
        }

        return emptyArray.slice.call(found);
    };

    //selector: empty string function object array and the instance of Jackey8
    jackey8.init = function (selector, context) {
        var dom;

        //if nothing given, return an empty collection
        if (!selector) {
            return jackey8.decorateDom();
        }

        if (typeof selector === 'string') {
            selector = selector.trim();

            //如果它是一个html片段，则创建一个节点
            //提示：在chrome21和Firefox15下面，
            // 如果不是<开头， 会抛错
            if (selector[0] === '<' && htmlFragmentRE.test(selector)) {
                //todo:
                dom = jackey8.createNodeByHtmlFragment(selector, RegExp.$1, context);
                selector = null;
            }
            //如果有parent，则先找到parent,然后使用find去找到selector
            else if (context !== void 0) {
                J8(context).find(selector);
            }
            else {
                // 正常的选取
                // 1 id 2 class 3 复杂选取
                // 作用域为document，因为如果存在context，则执行$(conetxt).find(selector)
                dom = jackey8.queryDom(document, selector);
            }
        } else if (type.isFunction(selector)) {
            J8(document).ready(selector);
        } else if (selector instanceof jackey8.decorateDom) {
            return selector;
        } else if (type.isArray(selector)) {
            dom = filterNullArray(selector);
        } else if (type.isObject(selector)) {
            dom = [selector];
            selector = null;
        } else {
            dom = [];
        }

        return jackey8.decorateDom(dom, selector);
    };

    J8 = function (selector, context) {
        return jackey8.init(selector, context);
    };

    J8.each = function (elements, callback) {
        var i, key;
        if (type.isArray(elements)) {
            for (i = 0; i < elements.length; i++) {
                if (callback.call(elements[i], i, elements[i]) === false) {
                    return elements;
                }
            }
        } else {
            for (key in elements) {
                if (callback.call(elements[key], key, elements[key]) === false) {
                    return elements;
                }
            }
        }
        return elements;
    }

    J8.fn = {
        forEach: emptyArray.forEach,
        reduce: emptyArray.reduce,
        push: emptyArray.push,
        sort: emptyArray.sort,
        indexOf: emptyArray.indexOf,
        concat: emptyArray.concat,
        ready: function (callback) {
            if (readyRE.test(document.readyState) && document.body) {
                callback(J8);
            } else {
                //todo: need to test DOMContentLoaded in each browsers
                document.addEventListener('DOMContentLoaded', function () {
                    callback(J8);
                }, false);
                return this;
            }
        }
    };

    return J8;

})(type);

window.Jackey8 = Jackey8;

if (window.J8 === void 0) {
    window.J8 = Jackey8;
}
<|endoftext|>


<commit_before>// Copyright (c) 2012 Titanium I.T. LLC. All rights reserved. See LICENSE.txt for details.

/*global desc, task, jake, fail, complete, directory*/
(function() {
	"use strict";

	if (!process.env.loose) console.log("For more forgiving test settings, use 'loose=true'");

	var lint = require("./build/lint/lint_runner.js");
	var nodeunit = require("nodeunit").reporters["default"];

	var NODE_VERSION = "v0.8.10";
	var SUPPORTED_BROWSERS = [
		"IE 8.0",
		"IE 9.0",
		"Firefox 15.0",
		"Chrome 22.0",
		"Safari 6.0",
		"Safari 5.1"  // iOS
	];

	var GENERATED_DIR = "generated";
	var TEMP_TESTFILE_DIR = GENERATED_DIR + "/test";

	directory(TEMP_TESTFILE_DIR);

	desc("Delete all generated files");
	task("clean", [], function() {
		jake.rmRf(GENERATED_DIR);
	});

	desc("Build and test");
	task("default", ["lint", "test"]);

	desc("Start Testacular server for testing");
	task("testacular", function() {
		sh("node node_modules/.bin/testacular start build/testacular.conf.js", "Could not start Testacular server", complete);
	}, {async: true});

	desc("Lint everything");
	task("lint", ["lintNode", "lintClient"]);

	task("lintNode", ["nodeVersion"], function() {
		var passed = lint.validateFileList(nodeFiles(), nodeLintOptions(), {});
		if (!passed) fail("Lint failed");
	});

	task("lintClient", function() {
		var passed = lint.validateFileList(clientFiles(), browserLintOptions(), {});
		if (!passed ) fail("Lint failed");
	});

	desc("Test everything");
	task("test", ["testNode", "testClient"]);

	desc("Test server code");
	task("testNode", ["nodeVersion", TEMP_TESTFILE_DIR], function() {
		nodeunit.run(nodeTestFiles(), null, function(failures) {
			if (failures) fail("Tests failed");
			complete();
		});
	}, {async: true});

	desc("Test client code");
	task("testClient", function() {
		sh("node node_modules/.bin/testacular run", "Client tests failed (run server with 'testacular')", function(output) {
			var browserMissing = false;
			SUPPORTED_BROWSERS.forEach(function(browser) {
				browserMissing = checkIfBrowserTested(browser, output) || browserMissing;
			});
			if (browserMissing && !process.env.loose) fail("Did not test all supported browsers");
			if (output.indexOf("TOTAL: 0 SUCCESS") !== -1) fail("Client tests did not run!");
		});
	}, {async: true});

	function checkIfBrowserTested(browser, output) {
		var missing = output.indexOf(browser + ": Executed") === -1;
		if (missing) console.log(browser + " was not tested!");
		return missing;
	}

	desc("Deploy to Heroku");
	task("deploy", ["default"], function() {
		console.log("1. Make sure 'git status' is clean.");
		console.log("2. 'git push heroku master'");
		console.log("3. 'jake test'");
	});

//	desc("Ensure correct version of Node is present.
	task("nodeVersion", [], function() {
		function failWithQualifier(qualifier) {
			fail("Incorrect node version. Expected " + qualifier +
					" [" + expectedString + "], but was [" + actualString + "].");
		}

		var expectedString = NODE_VERSION;
		var actualString = process.version;
		var expected = parseNodeVersion("expected Node version", expectedString);
		var actual = parseNodeVersion("Node version", actualString);

		if (!process.env.loose) {
			if (actual[0] !== expected[0] || actual[1] !== expected[1] || actual[2] !== expected[2]) {
				failWithQualifier("exactly");
			}
		}
		else {
			if (actual[0] < expected[0]) failWithQualifier("at least");
			if (actual[0] === expected[0] && actual[1] < expected[1]) failWithQualifier("at least");
			if (actual[0] === expected[0] && actual[1] === expected[1] && actual[2] < expected[2]) failWithQualifier("at least");
		}
	});

	desc("Integration checklist");
	task("integrate", ["default"], function() {
		console.log("1. Make sure 'git status' is clean.");
		console.log("2. Build on the integration box.");
		console.log("   a. Walk over to integration box.");
		console.log("   b. 'git pull'");
		console.log("   c. 'jake strict=true'");
		console.log("   d. If jake fails, stop! Try again after fixing the issue.");
		console.log("3. 'git checkout integration'");
		console.log("4. 'git merge master --no-ff --log'");
		console.log("5. 'git checkout master'");
	});

	desc("End-of-episode checklist");
	task("episode", [], function() {
		console.log("1. Save recording.");
		console.log("2. Double-check sound and framing.");
		console.log("3. Commit source code.");
		console.log("4. Check Windows compatibility")
		console.log("5. Tag episode: 'git tag -a episodeXX -m \"End of episode XX\"'");
	});

	function parseNodeVersion(description, versionString) {
		var versionMatcher = /^v(\d+)\.(\d+)\.(\d+)$/;    // v[major].[minor].[bugfix]
		var versionInfo = versionString.match(versionMatcher);
		if (versionInfo === null) fail("Could not parse " + description + " (was '" + versionString + "')");

		var major = parseInt(versionInfo[1], 10);
		var minor = parseInt(versionInfo[2], 10);
		var bugfix = parseInt(versionInfo[3], 10);
		return [major, minor, bugfix];
	}

	function sh(command, errorMessage, callback) {
		console.log("> " + command);

		var stdout = "";
		var process = jake.createExec(command, {printStdout:true, printStderr: true});
		process.on("stdout", function(chunk) {
			stdout += chunk;
		});
		process.on("error", function() {
			fail(errorMessage);
		});
		process.on("cmdEnd", function() {
			callback(stdout);
		});
		process.run();
	}

	function nodeFiles() {
		var javascriptFiles = new jake.FileList();
		javascriptFiles.include("**/*.js");
		javascriptFiles.exclude("node_modules");
		javascriptFiles.exclude("testacular.conf.js");
		javascriptFiles.exclude("src/client/**");
		return javascriptFiles.toArray();
	}

	function nodeTestFiles() {
		var testFiles = new jake.FileList();
		testFiles.include("**/_*_test.js");
		testFiles.exclude("node_modules");
		testFiles.exclude("src/client/**");
		testFiles = testFiles.toArray();
		return testFiles;
	}

	function clientFiles() {
		var javascriptFiles = new jake.FileList();
		javascriptFiles.include("src/client/**/*.js");
		return javascriptFiles.toArray();
	}

	function nodeLintOptions() {
		var options = globalLintOptions();
		options.node = true;
		return options;
	}

	function browserLintOptions() {
		var options = globalLintOptions();
		options.browser = true;
		return options;
	}

	function globalLintOptions() {
		var options = {
			bitwise:true,
			curly:false,
			eqeqeq:true,
			forin:true,
			immed:true,
			latedef:true,
			newcap:true,
			noarg:true,
			noempty:true,
			nonew:true,
			regexp:true,
			undef:true,
			strict:true,
			trailing:true
		};
		return options;
	}

}());
<commit_msg>Fixed Jakefile Lint error and made test failures more helpful<commit_after>// Copyright (c) 2012 Titanium I.T. LLC. All rights reserved. See LICENSE.txt for details.

/*global desc, task, jake, fail, complete, directory*/
(function() {
	"use strict";

	if (!process.env.loose) console.log("For more forgiving test settings, use 'loose=true'");

	var lint = require("./build/lint/lint_runner.js");
	var nodeunit = require("nodeunit").reporters["default"];

	var NODE_VERSION = "v0.8.10";
	var SUPPORTED_BROWSERS = [
		"IE 8.0",
		"IE 9.0",
		"Firefox 15.0",
		"Chrome 22.0",
		"Safari 6.0",
		"Safari 5.1"  // iOS
	];

	var GENERATED_DIR = "generated";
	var TEMP_TESTFILE_DIR = GENERATED_DIR + "/test";

	directory(TEMP_TESTFILE_DIR);

	desc("Delete all generated files");
	task("clean", [], function() {
		jake.rmRf(GENERATED_DIR);
	});

	desc("Build and test");
	task("default", ["lint", "test"]);

	desc("Start Testacular server for testing");
	task("testacular", function() {
		sh("node node_modules/.bin/testacular start build/testacular.conf.js", "Could not start Testacular server", complete);
	}, {async: true});

	desc("Lint everything");
	task("lint", ["lintNode", "lintClient"]);

	task("lintNode", ["nodeVersion"], function() {
		var passed = lint.validateFileList(nodeFiles(), nodeLintOptions(), {});
		if (!passed) fail("Lint failed");
	});

	task("lintClient", function() {
		var passed = lint.validateFileList(clientFiles(), browserLintOptions(), {});
		if (!passed ) fail("Lint failed");
	});

	desc("Test everything");
	task("test", ["testNode", "testClient"]);

	desc("Test server code");
	task("testNode", ["nodeVersion", TEMP_TESTFILE_DIR], function() {
		nodeunit.run(nodeTestFiles(), null, function(failures) {
			if (failures) fail("Tests failed");
			complete();
		});
	}, {async: true});

	desc("Test client code");
	task("testClient", function() {
		sh("node node_modules/.bin/testacular run", "Client tests failed (to start server, run 'jake testacular')", function(output) {
			var browserMissing = false;
			SUPPORTED_BROWSERS.forEach(function(browser) {
				browserMissing = checkIfBrowserTested(browser, output) || browserMissing;
			});
			if (browserMissing && !process.env.loose) fail("Did not test all supported browsers (use 'loose=true' to suppress error)");
			if (output.indexOf("TOTAL: 0 SUCCESS") !== -1) fail("Client tests did not run!");
		});
	}, {async: true});

	function checkIfBrowserTested(browser, output) {
		var missing = output.indexOf(browser + ": Executed") === -1;
		if (missing) console.log(browser + " was not tested!");
		return missing;
	}

	desc("Deploy to Heroku");
	task("deploy", ["default"], function() {
		console.log("1. Make sure 'git status' is clean.");
		console.log("2. 'git push heroku master'");
		console.log("3. 'jake test'");
	});

//	desc("Ensure correct version of Node is present.
	task("nodeVersion", [], function() {
		function failWithQualifier(qualifier) {
			fail("Incorrect node version. Expected " + qualifier +
					" [" + expectedString + "], but was [" + actualString + "].");
		}

		var expectedString = NODE_VERSION;
		var actualString = process.version;
		var expected = parseNodeVersion("expected Node version", expectedString);
		var actual = parseNodeVersion("Node version", actualString);

		if (!process.env.loose) {
			if (actual[0] !== expected[0] || actual[1] !== expected[1] || actual[2] !== expected[2]) {
				failWithQualifier("exactly");
			}
		}
		else {
			if (actual[0] < expected[0]) failWithQualifier("at least");
			if (actual[0] === expected[0] && actual[1] < expected[1]) failWithQualifier("at least");
			if (actual[0] === expected[0] && actual[1] === expected[1] && actual[2] < expected[2]) failWithQualifier("at least");
		}
	});

	desc("Integration checklist");
	task("integrate", ["default"], function() {
		console.log("1. Make sure 'git status' is clean.");
		console.log("2. Build on the integration box.");
		console.log("   a. Walk over to integration box.");
		console.log("   b. 'git pull'");
		console.log("   c. 'jake strict=true'");
		console.log("   d. If jake fails, stop! Try again after fixing the issue.");
		console.log("3. 'git checkout integration'");
		console.log("4. 'git merge master --no-ff --log'");
		console.log("5. 'git checkout master'");
	});

	desc("End-of-episode checklist");
	task("episode", [], function() {
		console.log("1. Save recording.");
		console.log("2. Double-check sound and framing.");
		console.log("3. Commit source code.");
		console.log("4. Check Windows compatibility");
		console.log("5. Tag episode: 'git tag -a episodeXX -m \"End of episode XX\"'");
	});

	function parseNodeVersion(description, versionString) {
		var versionMatcher = /^v(\d+)\.(\d+)\.(\d+)$/;    // v[major].[minor].[bugfix]
		var versionInfo = versionString.match(versionMatcher);
		if (versionInfo === null) fail("Could not parse " + description + " (was '" + versionString + "')");

		var major = parseInt(versionInfo[1], 10);
		var minor = parseInt(versionInfo[2], 10);
		var bugfix = parseInt(versionInfo[3], 10);
		return [major, minor, bugfix];
	}

	function sh(command, errorMessage, callback) {
		console.log("> " + command);

		var stdout = "";
		var process = jake.createExec(command, {printStdout:true, printStderr: true});
		process.on("stdout", function(chunk) {
			stdout += chunk;
		});
		process.on("error", function() {
			fail(errorMessage);
		});
		process.on("cmdEnd", function() {
			callback(stdout);
		});
		process.run();
	}

	function nodeFiles() {
		var javascriptFiles = new jake.FileList();
		javascriptFiles.include("**/*.js");
		javascriptFiles.exclude("node_modules");
		javascriptFiles.exclude("testacular.conf.js");
		javascriptFiles.exclude("src/client/**");
		return javascriptFiles.toArray();
	}

	function nodeTestFiles() {
		var testFiles = new jake.FileList();
		testFiles.include("**/_*_test.js");
		testFiles.exclude("node_modules");
		testFiles.exclude("src/client/**");
		testFiles = testFiles.toArray();
		return testFiles;
	}

	function clientFiles() {
		var javascriptFiles = new jake.FileList();
		javascriptFiles.include("src/client/**/*.js");
		return javascriptFiles.toArray();
	}

	function nodeLintOptions() {
		var options = globalLintOptions();
		options.node = true;
		return options;
	}

	function browserLintOptions() {
		var options = globalLintOptions();
		options.browser = true;
		return options;
	}

	function globalLintOptions() {
		var options = {
			bitwise:true,
			curly:false,
			eqeqeq:true,
			forin:true,
			immed:true,
			latedef:true,
			newcap:true,
			noarg:true,
			noempty:true,
			nonew:true,
			regexp:true,
			undef:true,
			strict:true,
			trailing:true
		};
		return options;
	}

}());
<|endoftext|>


<commit_before>/*
Leaflet building, testing and linting scripts.

To use, install Node, then run the following commands in the project root:

    npm install -g jake
    npm install

To check the code for errors and build Leaflet from source, run "jake".
To run the tests, run "jake test".

For a custom build, open build/build.html in the browser and follow the instructions.
*/

var build = require('./build/build.js'),
    version = require('./src/Leaflet.js').version;

function hint(msg, paths) {
	return function () {
		console.log(msg);
		jake.exec('node node_modules/jshint/bin/jshint -c ' + paths,
				{printStdout: true}, function () {
			console.log('\tCheck passed.\n');
			complete();
		});
	}
}

desc('Check Leaflet source for errors with JSHint');
task('lint', {async: true}, hint('Checking for JS errors...', 'build/hintrc.js src'));

desc('Check Leaflet specs source for errors with JSHint');
task('lintspec', {async: true}, hint('Checking for specs JS errors...', 'spec/spec.hintrc.js spec/suites'));

desc('Combine and compress Leaflet source files');
task('build', {async: true}, function (compsBase32, buildName) {
	var v;

	jake.exec('git log -1 --pretty=format:"%h"', {}, function () {
		build.build(complete, v, compsBase32, buildName);

	}).on('stdout', function (data) {
		v = version + ' (' + data.toString() + ')';
	})
});

desc('Run PhantomJS tests');
task('test', ['lint', 'lintspec'], {async: true}, function () {
	build.test(complete);
});

task('default', ['test', 'build']);

jake.addListener('complete', function () {
  process.exit();
});
<commit_msg>Fixed jake build if leaflet copy is not a git repository<commit_after>/*
Leaflet building, testing and linting scripts.

To use, install Node, then run the following commands in the project root:

    npm install -g jake
    npm install

To check the code for errors and build Leaflet from source, run "jake".
To run the tests, run "jake test".

For a custom build, open build/build.html in the browser and follow the instructions.
*/

var build = require('./build/build.js'),
    version = require('./src/Leaflet.js').version;

function hint(msg, paths) {
	return function () {
		console.log(msg);
		jake.exec('node node_modules/jshint/bin/jshint -c ' + paths,
				{printStdout: true}, function () {
			console.log('\tCheck passed.\n');
			complete();
		});
	};
}

desc('Check Leaflet source for errors with JSHint');
task('lint', {async: true}, hint('Checking for JS errors...', 'build/hintrc.js src'));

desc('Check Leaflet specs source for errors with JSHint');
task('lintspec', {async: true}, hint('Checking for specs JS errors...', 'spec/spec.hintrc.js spec/suites'));

desc('Combine and compress Leaflet source files');
task('build', {async: true}, function (compsBase32, buildName) {
	var v;

	jake.exec('git log -1 --pretty=format:"%h"', {breakOnError: false}, function () {
		build.build(complete, v, compsBase32, buildName);

	}).on('stdout', function (data) {
		v = version + ' (' + data.toString() + ')';
	}).on('error', function () {
		v = version;
	});
});

desc('Run PhantomJS tests');
task('test', ['lint', 'lintspec'], {async: true}, function () {
	build.test(complete);
});

task('default', ['test', 'build']);

jake.addListener('complete', function () {
  process.exit();
});
<|endoftext|>


<commit_before>/*
 * Jakefile for Rimu Markup (http://github.com/srackham/rimu).
 */
'use strict';

var pkg = require('./package.json');
var shelljs = require('shelljs');
var child_process = require('child_process');

/* Inputs and outputs */

var RIMU_JS = 'bin/rimu.js';
var MAIN_TS = 'src/main.ts';
var RIMU_MIN_JS = 'bin/rimu.min.js';
var SOURCE = shelljs.ls('src/*.ts');
var TESTS = shelljs.ls('test/*.js');
var TYPEDOC_DIR = 'doc/api';
var TYPEDOC_INDEX = TYPEDOC_DIR + '/index.html';
var GH_PAGES_DIR = './gh-pages/';

var DOCS = [
  {src: 'README.md', dst: 'doc/index.html', title: 'Rimu Markup'},
  {src: 'doc/tips.rmu', dst: 'doc/tips.html', title: 'Rimu Tips'}
];

var HTML = ['bin/rimuplayground.html'];
DOCS.forEach(function(doc) {
  HTML.push(doc.dst);
});

/* Utility functions. */

/*
 Execute shell commands in parallel then run the callback when they have all finished.
 `callback` defaults to the Jake async `complete` function.
 Abort if an error occurs.
 Write command output to the inherited stdout (unless the Jake --quiet option is set).
 Print a status message when each command starts and finishes (unless the Jake --quiet option is set).

 NOTE: This function is similar to the built-in jake.exec function but is twice as fast.
 */
function exec(commands, callback) {
  if (typeof commands === 'string') {
    commands = [commands];
  }
  callback = callback || complete;
  var remaining = commands.length;
  if (remaining === 0) {
    callback();
  } else {
    commands.forEach(function(command) {
      jake.logger.log('Starting: ' + command);
      child_process.exec(command, function(error, stdout, stderr) {
        if (!jake.program.opts.quiet) {
          process.stdout.write(stdout);
        }
        if (error !== null) {
          fail(error, error.code);
        }
        jake.logger.log('Finished: ' + command);
        remaining--;
        if (remaining === 0) {
          callback();
        }
      });
    });
  }
}

/*
 Tasks

 All tasks are synchronous (another task will not run until the current task has completed).
 Consequently all task dependencies are executed asynchronously in declaration order.
 The `exec` function ensures shell commands within each task run in parallel.
 */

desc('Run test task.');
task('default', ['test']);

desc('compile, jslint, test, tslint, build-gh-pages, validate-html.');
task('build', ['test', 'tslint', 'build-gh-pages', 'validate-html']);

desc('Update version number, tag and push to Github and npm. Use vers=x.y.z argument to set a new version number.');
task('release', ['build', 'version', 'tag', 'publish']);

desc('Lint Javascript and JSON files.');
task('jslint', {async: true}, function() {
  var commands = TESTS.map(function(file) {
    return 'jshint ' + file;
  });
  commands.push('jsonlint --quiet package.json');
  exec(commands);
});

desc('Lint TypeScript source files.');
task('tslint', {async: true}, function() {
  var commands = SOURCE.map(function(file) {
    return 'tslint -f ' + file;
  });
  exec(commands);
});

desc('Run tests (recompile if necessary).');
task('test', ['compile', 'jslint'], {async: true}, function() {
  var commands = TESTS.map(function(file) {
    return 'nodeunit ' + file;
  });
  exec(commands);
});

desc('Compile Typescript to JavaScript then uglify.');
task('compile', [RIMU_JS, RIMU_MIN_JS]);

file(RIMU_JS, SOURCE, {async: true}, function() {
  exec('tsc --noImplicitAny --out ' + RIMU_JS + ' ' + MAIN_TS);
});

file(RIMU_MIN_JS, [RIMU_JS], {async: true}, function() {
  var preamble = '/* ' + pkg.name + ' ' + pkg.version + ' (' + pkg.repository.url + ') */';
  var command = 'uglifyjs  --preamble "' + preamble + '" --output ' + RIMU_MIN_JS + ' ' + RIMU_JS;
  exec(command);
});

desc('Create TypeDoc API documentation.');
task('api-docs', [TYPEDOC_INDEX]);

file(TYPEDOC_INDEX, SOURCE, {async: true}, function() {
  shelljs.rm('-rf', TYPEDOC_DIR);
  exec('typedoc --out ' + TYPEDOC_DIR + ' ./src');
});

desc('Generate HTML documentation');
task('html-docs', {async: true}, function() {
  var commands = DOCS.map(function(doc) {
    return 'node ./bin/rimuc.js --output ' + doc.dst +
      ' --prepend "{--title}=\'' + doc.title + '\'"' +
      ' doc/doc-header.rmu ' + doc.src + ' doc/doc-footer.rmu';
  });
  exec(commands);
});

desc('Validate HTML documents with W3C Validator.');
task('validate-html', {async: true}, function() {
  var commands = HTML.map(function(file) {
    return 'w3cjs validate ' + file;
  });
  exec(commands);
});

desc('Display or update the project version number. Use vers=x.y.z argument to set a new version number.');
task('version', {async: true}, function() {
  var version = process.env.vers;
  if (!version) {
    console.log('\nversion: ' + pkg.version);
    complete();
  }
  else {
    if (!version.match(/^\d+\.\d+\.\d+$/)) {
      fail('Invalid version number: ' + version + '\n');
    }
    shelljs.sed('-i', /(\n\s*"version"\s*:\s*)"\d+\.\d+\.\d+"/, '$1' + '"' + version + '"', 'package.json');
    pkg.version = version;
    exec('git commit -m "Bump version number." package.json');
  }
});

desc('Create tag ' + pkg.version);
task('tag', ['test'], {async: true}, function() {
  exec('git tag -a -m "Tag ' + pkg.version + '" ' + pkg.version);
});

desc('Commit changes to local Git repo.');
task('commit', ['test'], {async: true}, function() {
  jake.exec('git commit -a', {interactive: true}, complete);
});

desc('push, publish-npm.');
task('publish', ['push', 'publish-npm']);

desc('Push local commits to Github.');
task('push', ['test'], {async: true}, function() {
  exec('git push -u --tags origin master');
});

desc('Publish to npm.');
task('publish-npm', {async: true}, ['test'], function() {
  exec('npm publish');
});

desc('Rebuild and validate documentation then commit and publish to GitHub Pages');
task('release-gh-pages', ['build-gh-pages', 'commit-gh-pages', 'push-gh-pages']);

desc('Generate documentation and copy to gh-pages repo');
task('build-gh-pages', ['html-docs', 'validate-html'], function() {
  shelljs.cp('-f', HTML.concat(RIMU_JS), GH_PAGES_DIR)
});

desc('Commit changes to local Github Pages repo. Use msg=\'commit message\' to set a custom commit message.');
task('commit-gh-pages', ['test'], {async: true}, function() {
  var msg = process.env.msg;
  if (!msg) {
    msg = 'Rebuilt project website.';
  }
  shelljs.cd(GH_PAGES_DIR);
  exec('git commit -a -m "' + msg + '"');
});

desc('Push Github Pages commits to Github.');
task('push-gh-pages', ['test'], {async: true}, function() {
  shelljs.cd(GH_PAGES_DIR);
  exec('git push origin gh-pages');
});
<commit_msg>Jake 'release' task also rebuilds and updates documentation website.<commit_after>/*
 * Jakefile for Rimu Markup (http://github.com/srackham/rimu).
 */
'use strict';

var pkg = require('./package.json');
var shelljs = require('shelljs');
var child_process = require('child_process');

/* Inputs and outputs */

var RIMU_JS = 'bin/rimu.js';
var MAIN_TS = 'src/main.ts';
var RIMU_MIN_JS = 'bin/rimu.min.js';
var SOURCE = shelljs.ls('src/*.ts');
var TESTS = shelljs.ls('test/*.js');
var TYPEDOC_DIR = 'doc/api';
var TYPEDOC_INDEX = TYPEDOC_DIR + '/index.html';
var GH_PAGES_DIR = './gh-pages/';

var DOCS = [
  {src: 'README.md', dst: 'doc/index.html', title: 'Rimu Markup'},
  {src: 'doc/tips.rmu', dst: 'doc/tips.html', title: 'Rimu Tips'}
];

var HTML = ['bin/rimuplayground.html'];
DOCS.forEach(function(doc) {
  HTML.push(doc.dst);
});

/* Utility functions. */

/*
 Execute shell commands in parallel then run the callback when they have all finished.
 `callback` defaults to the Jake async `complete` function.
 Abort if an error occurs.
 Write command output to the inherited stdout (unless the Jake --quiet option is set).
 Print a status message when each command starts and finishes (unless the Jake --quiet option is set).

 NOTE: This function is similar to the built-in jake.exec function but is twice as fast.
 */
function exec(commands, callback) {
  if (typeof commands === 'string') {
    commands = [commands];
  }
  callback = callback || complete;
  var remaining = commands.length;
  if (remaining === 0) {
    callback();
  } else {
    commands.forEach(function(command) {
      jake.logger.log('Starting: ' + command);
      child_process.exec(command, function(error, stdout, stderr) {
        if (!jake.program.opts.quiet) {
          process.stdout.write(stdout);
        }
        if (error !== null) {
          fail(error, error.code);
        }
        jake.logger.log('Finished: ' + command);
        remaining--;
        if (remaining === 0) {
          callback();
        }
      });
    });
  }
}

/*
 Tasks

 All tasks are synchronous (another task will not run until the current task has completed).
 Consequently all task dependencies are executed asynchronously in declaration order.
 The `exec` function ensures shell commands within each task run in parallel.
 */

desc('Run test task.');
task('default', ['test']);

desc('compile, jslint, test, tslint, build-gh-pages, validate-html.');
task('build', ['test', 'tslint', 'build-gh-pages', 'validate-html']);

desc('Update version number, tag and push to Github and npm. Use vers=x.y.z argument to set a new version number. Finally, rebuild and publish docs website.');
task('release', ['build', 'version', 'tag', 'publish', 'release-gh-pages']);

desc('Lint Javascript and JSON files.');
task('jslint', {async: true}, function() {
  var commands = TESTS.map(function(file) {
    return 'jshint ' + file;
  });
  commands.push('jsonlint --quiet package.json');
  exec(commands);
});

desc('Lint TypeScript source files.');
task('tslint', {async: true}, function() {
  var commands = SOURCE.map(function(file) {
    return 'tslint -f ' + file;
  });
  exec(commands);
});

desc('Run tests (recompile if necessary).');
task('test', ['compile', 'jslint'], {async: true}, function() {
  var commands = TESTS.map(function(file) {
    return 'nodeunit ' + file;
  });
  exec(commands);
});

desc('Compile Typescript to JavaScript then uglify.');
task('compile', [RIMU_JS, RIMU_MIN_JS]);

file(RIMU_JS, SOURCE, {async: true}, function() {
  exec('tsc --noImplicitAny --out ' + RIMU_JS + ' ' + MAIN_TS);
});

file(RIMU_MIN_JS, [RIMU_JS], {async: true}, function() {
  var preamble = '/* ' + pkg.name + ' ' + pkg.version + ' (' + pkg.repository.url + ') */';
  var command = 'uglifyjs  --preamble "' + preamble + '" --output ' + RIMU_MIN_JS + ' ' + RIMU_JS;
  exec(command);
});

desc('Create TypeDoc API documentation.');
task('api-docs', [TYPEDOC_INDEX]);

file(TYPEDOC_INDEX, SOURCE, {async: true}, function() {
  shelljs.rm('-rf', TYPEDOC_DIR);
  exec('typedoc --out ' + TYPEDOC_DIR + ' ./src');
});

desc('Generate HTML documentation');
task('html-docs', {async: true}, function() {
  var commands = DOCS.map(function(doc) {
    return 'node ./bin/rimuc.js --output ' + doc.dst +
      ' --prepend "{--title}=\'' + doc.title + '\'"' +
      ' doc/doc-header.rmu ' + doc.src + ' doc/doc-footer.rmu';
  });
  exec(commands);
});

desc('Validate HTML documents with W3C Validator.');
task('validate-html', {async: true}, function() {
  var commands = HTML.map(function(file) {
    return 'w3cjs validate ' + file;
  });
  exec(commands);
});

desc('Display or update the project version number. Use vers=x.y.z argument to set a new version number.');
task('version', {async: true}, function() {
  var version = process.env.vers;
  if (!version) {
    console.log('\nversion: ' + pkg.version);
    complete();
  }
  else {
    if (!version.match(/^\d+\.\d+\.\d+$/)) {
      fail('Invalid version number: ' + version + '\n');
    }
    shelljs.sed('-i', /(\n\s*"version"\s*:\s*)"\d+\.\d+\.\d+"/, '$1' + '"' + version + '"', 'package.json');
    pkg.version = version;
    exec('git commit -m "Bump version number." package.json');
  }
});

desc('Create tag ' + pkg.version);
task('tag', ['test'], {async: true}, function() {
  exec('git tag -a -m "Tag ' + pkg.version + '" ' + pkg.version);
});

desc('Commit changes to local Git repo.');
task('commit', ['test'], {async: true}, function() {
  jake.exec('git commit -a', {interactive: true}, complete);
});

desc('push, publish-npm.');
task('publish', ['push', 'publish-npm']);

desc('Push local commits to Github.');
task('push', ['test'], {async: true}, function() {
  exec('git push -u --tags origin master');
});

desc('Publish to npm.');
task('publish-npm', {async: true}, ['test'], function() {
  exec('npm publish');
});

desc('Rebuild and validate documentation then commit and publish to GitHub Pages');
task('release-gh-pages', ['build-gh-pages', 'commit-gh-pages', 'push-gh-pages']);

desc('Generate documentation and copy to gh-pages repo');
task('build-gh-pages', ['html-docs', 'validate-html'], function() {
  shelljs.cp('-f', HTML.concat(RIMU_JS), GH_PAGES_DIR)
});

desc('Commit changes to local Github Pages repo. Use msg=\'commit message\' to set a custom commit message.');
task('commit-gh-pages', ['test'], {async: true}, function() {
  var msg = process.env.msg;
  if (!msg) {
    msg = 'Rebuilt project website.';
  }
  shelljs.cd(GH_PAGES_DIR);
  exec('git commit -a -m "' + msg + '"');
});

desc('Push Github Pages commits to Github.');
task('push-gh-pages', ['test'], {async: true}, function() {
  shelljs.cd(GH_PAGES_DIR);
  exec('git push origin gh-pages');
});
<|endoftext|>