<commit_before>/**
 * @license
 * Copyright 2011 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @fileoverview Object representing a code comment.
 * @author fraser@google.com (Neil Fraser)
 */
'use strict';

goog.provide('Blockly.Comment');

goog.require('Blockly.Bubble');
goog.require('Blockly.Css');
goog.require('Blockly.Events');
goog.require('Blockly.Events.BlockChange');
goog.require('Blockly.Events.BubbleOpen');
goog.require('Blockly.Icon');
goog.require('Blockly.utils.deprecation');
goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.object');
goog.require('Blockly.utils.Svg');
goog.require('Blockly.utils.userAgent');
goog.require('Blockly.Warning');

/**
 * Class for a comment.
 * @param {!Blockly.Block} block The block associated with this comment.
 * @extends {Blockly.Icon}
 * @constructor
 */
Blockly.Comment = function(block) {
  Blockly.Comment.superClass_.constructor.call(this, block);

  /**
   * The model for this comment.
   * @type {!Blockly.Block.CommentModel}
   * @private
   */
  this.model_ = block.commentModel;
  // If someone creates the comment directly instead of calling
  // block.setCommentText we want to make sure the text is non-null;
  this.model_.text = this.model_.text || '';

  /**
   * The model's text value at the start of an edit.
   * Used to tell if an event should be fired at the end of an edit.
   * @type {?string}
   * @private
   */
  this.cachedText_ = '';

  /**
   * Mouse up event data.
   * @type {?Blockly.EventData}
   * @private
   */
  this.onMouseUpWrapper_ = null;

  /**
   * Wheel event data.
   * @type {?Blockly.EventData}
   * @private
   */
  this.onWheelWrapper_ = null;

  /**
   * Change event data.
   * @type {?Blockly.EventData}
   * @private
   */
  this.onChangeWrapper_ = null;

  /**
   * Input event data.
   * @type {?Blockly.EventData}
   * @private
   */
  this.onInputWrapper_ = null;

  this.createIcon();
};
Blockly.utils.object.inherits(Blockly.Comment, Blockly.Icon);

/**
 * Draw the comment icon.
 * @param {!Element} group The icon group.
 * @protected
 */
Blockly.Comment.prototype.drawIcon_ = function(group) {
  // Circle.
  Blockly.utils.dom.createSvgElement(
      Blockly.utils.Svg.CIRCLE,
      {'class': 'blocklyIconShape', 'r': '8', 'cx': '8', 'cy': '8'},
      group);
  // Can't use a real '?' text character since different browsers and operating
  // systems render it differently.
  // Body of question mark.
  Blockly.utils.dom.createSvgElement(
      Blockly.utils.Svg.PATH,
      {
        'class': 'blocklyIconSymbol',
        'd': 'm6.8,10h2c0.003,-0.617 0.271,-0.962 0.633,-1.266 2.875,-2.405' +
          '0.607,-5.534 -3.765,-3.874v1.7c3.12,-1.657 3.698,0.118 2.336,1.25' +
          '-1.201,0.998 -1.201,1.528 -1.204,2.19z'},
      group);
  // Dot of question mark.
  Blockly.utils.dom.createSvgElement(
      Blockly.utils.Svg.RECT,
      {
        'class': 'blocklyIconSymbol',
        'x': '6.8',
        'y': '10.78',
        'height': '2',
        'width': '2'
      },
      group);
};

/**
 * Create the editor for the comment's bubble.
 * @return {!SVGElement} The top-level node of the editor.
 * @private
 */
Blockly.Comment.prototype.createEditor_ = function() {
  /* Create the editor.  Here's the markup that will be generated in
   * editable mode:
    <foreignObject x="8" y="8" width="164" height="164">
      <body xmlns="http://www.w3.org/1999/xhtml" class="blocklyMinimalBody">
        <textarea xmlns="http://www.w3.org/1999/xhtml"
            class="blocklyCommentTextarea"
            style="height: 164px; width: 164px;"></textarea>
      </body>
    </foreignObject>
   * For non-editable mode see Warning.textToDom_.
   */

  this.foreignObject_ = Blockly.utils.dom.createSvgElement(
      Blockly.utils.Svg.FOREIGNOBJECT,
      {'x': Blockly.Bubble.BORDER_WIDTH, 'y': Blockly.Bubble.BORDER_WIDTH},
      null);

  var body = document.createElementNS(Blockly.utils.dom.HTML_NS, 'body');
  body.setAttribute('xmlns', Blockly.utils.dom.HTML_NS);
  body.className = 'blocklyMinimalBody';

  this.textarea_ = document.createElementNS(
      Blockly.utils.dom.HTML_NS, 'textarea');
  var textarea = this.textarea_;
  textarea.className = 'blocklyCommentTextarea';
  textarea.setAttribute('dir', this.block_.RTL ? 'RTL' : 'LTR');
  textarea.value = this.model_.text;
  this.resizeTextarea_();

  body.appendChild(textarea);
  this.foreignObject_.appendChild(body);

  // Ideally this would be hooked to the focus event for the comment.
  // However doing so in Firefox swallows the cursor for unknown reasons.
  // So this is hooked to mouseup instead.  No big deal.
  this.onMouseUpWrapper_ = Blockly.bindEventWithChecks_(
      textarea, 'mouseup', this, this.startEdit_, true, true);
  // Don't zoom with mousewheel.
  this.onWheelWrapper_ = Blockly.bindEventWithChecks_(
      textarea, 'wheel', this, function(e) {
        e.stopPropagation();
      });
  this.onChangeWrapper_ = Blockly.bindEventWithChecks_(
      textarea, 'change', this, function(_e) {
        if (this.cachedText_ != this.model_.text) {
          Blockly.Events.fire(new Blockly.Events.BlockChange(
              this.block_, 'comment', null, this.cachedText_, this.model_.text));
        }
      });
  this.onInputWrapper_ = Blockly.bindEventWithChecks_(
      textarea, 'input', this, function(_e) {
        this.model_.text = textarea.value;
      });

  setTimeout(textarea.focus.bind(textarea), 0);

  return this.foreignObject_;
};

/**
 * Add or remove editability of the comment.
 * @override
 */
Blockly.Comment.prototype.updateEditable = function() {
  Blockly.Comment.superClass_.updateEditable.call(this);
  if (this.isVisible()) {
    // Recreate the bubble with the correct UI.
    this.disposeBubble_();
    this.createBubble_();
  }
};

/**
 * Callback function triggered when the bubble has resized.
 * Resize the text area accordingly.
 * @private
 */
Blockly.Comment.prototype.onBubbleResize_ = function() {
  if (!this.isVisible()) {
    return;
  }
  this.model_.size = this.bubble_.getBubbleSize();
  this.resizeTextarea_();
};

/**
 * Resizes the text area to match the size defined on the model (which is
 * the size of the bubble).
 * @private
 */
Blockly.Comment.prototype.resizeTextarea_ = function() {
  var size = this.model_.size;
  var doubleBorderWidth = 2 * Blockly.Bubble.BORDER_WIDTH;
  var widthMinusBorder = size.width - doubleBorderWidth;
  var heightMinusBorder = size.height - doubleBorderWidth;
  this.foreignObject_.setAttribute('width', widthMinusBorder);
  this.foreignObject_.setAttribute('height', heightMinusBorder);
  this.textarea_.style.width = (widthMinusBorder - 4) + 'px';
  this.textarea_.style.height = (heightMinusBorder - 4) + 'px';
};

/**
 * Show or hide the comment bubble.
 * @param {boolean} visible True if the bubble should be visible.
 */
Blockly.Comment.prototype.setVisible = function(visible) {
  if (visible == this.isVisible()) {
    return;
  }
  Blockly.Events.fire(
      new Blockly.Events.BubbleOpen(this.block_, visible, 'comment'));
  this.model_.pinned = visible;
  if (visible) {
    this.createBubble_();
  } else {
    this.disposeBubble_();
  }
};

/**
 * Show the bubble. Handles deciding if it should be editable or not.
 * @private
 */
Blockly.Comment.prototype.createBubble_ = function() {
  if (!this.block_.isEditable() || Blockly.utils.userAgent.IE) {
    // Steal the code from warnings to make an uneditable text bubble.
    // MSIE does not support foreignobject; textareas are impossible.
    // https://docs.microsoft.com/en-us/openspecs/ie_standards/ms-svg/56e6e04c-7c8c-44dd-8100-bd745ee42034
    // Always treat comments in IE as uneditable.
    this.createNonEditableBubble_();
  } else {
    this.createEditableBubble_();
  }
};

/**
 * Show an editable bubble.
 * @private
 */
Blockly.Comment.prototype.createEditableBubble_ = function() {
  this.bubble_ = new Blockly.Bubble(
      /** @type {!Blockly.WorkspaceSvg} */ (this.block_.workspace),
      this.createEditor_(), this.block_.pathObject.svgPath,
      /** @type {!Blockly.utils.Coordinate} */ (this.iconXY_),
      this.model_.size.width, this.model_.size.height);
  // Expose this comment's block's ID on its top-level SVG group.
  this.bubble_.setSvgId(this.block_.id);
  this.bubble_.registerResizeEvent(this.onBubbleResize_.bind(this));
  this.applyColour();
};

/**
 * Show a non-editable bubble.
 * @private
 * @suppress {checkTypes} Suppress `this` type mismatch.
 */
Blockly.Comment.prototype.createNonEditableBubble_ = function() {
  // TODO (#2917): It would be great if the comment could support line breaks.
  Blockly.Warning.prototype.createBubble.call(this);
};

/**
 * Dispose of the bubble.
 * @private
 * @suppress {checkTypes} Suppress `this` type mismatch.
 */
Blockly.Comment.prototype.disposeBubble_ = function() {
  if (this.paragraphElement_) {
    // We're using the warning UI so we have to let it dispose.
    Blockly.Warning.prototype.disposeBubble.call(this);
    return;
  }
  if (this.onMouseUpWrapper_) {
    Blockly.unbindEvent_(this.onMouseUpWrapper_);
    this.onMouseUpWrapper_ = null;
  }
  if (this.onWheelWrapper_) {
    Blockly.unbindEvent_(this.onWheelWrapper_);
    this.onWheelWrapper_ = null;
  }
  if (this.onChangeWrapper_) {
    Blockly.unbindEvent_(this.onChangeWrapper_);
    this.onChangeWrapper_ = null;
  }
  if (this.onInputWrapper_) {
    Blockly.unbindEvent_(this.onInputWrapper_);
    this.onInputWrapper_ = null;
  }
  this.bubble_.dispose();
  this.bubble_ = null;
  this.textarea_ = null;
  this.foreignObject_ = null;
};

/**
 * Callback fired when an edit starts.
 *
 * Bring the comment to the top of the stack when clicked on. Also cache the
 * current text so it can be used to fire a change event.
 * @param {!Event} _e Mouse up event.
 * @private
 */
Blockly.Comment.prototype.startEdit_ = function(_e) {
  if (this.bubble_.promote()) {
    // Since the act of moving this node within the DOM causes a loss of focus,
    // we need to reapply the focus.
    this.textarea_.focus();
  }

  this.cachedText_ = this.model_.text;
};

/**
 * Get the dimensions of this comment's bubble.
 * @return {Blockly.utils.Size} Object with width and height properties.
 */
Blockly.Comment.prototype.getBubbleSize = function() {
  return this.model_.size;
};

/**
 * Size this comment's bubble.
 * @param {number} width Width of the bubble.
 * @param {number} height Height of the bubble.
 */
Blockly.Comment.prototype.setBubbleSize = function(width, height) {
  if (this.bubble_) {
    this.bubble_.setBubbleSize(width, height);
  } else {
    this.model_.size.width = width;
    this.model_.size.height = height;
  }
};

/**
 * Returns this comment's text.
 * @return {string} Comment text.
 * @deprecated August 2019 Use block.getCommentText() instead.
 */
Blockly.Comment.prototype.getText = function() {
  Blockly.utils.deprecation.warn(
      'Comment.prototype.getText',
      'August 2019',
      'December 2020',
      'block.getCommentText');
  return this.model_.text || '';
};

/**
 * Set this comment's text.
 *
 * If you want to receive a comment change event, then this should not be called
 * directly. Instead call block.setCommentText();
 * @param {string} text Comment text.
 * @deprecated August 2019 Use block.setCommentText() instead.
 */
Blockly.Comment.prototype.setText = function(text) {
  Blockly.utils.deprecation.warn(
      'Comment.prototype.setText',
      'August 2019',
      'December 2020',
      'block.setCommentText');
  if (this.model_.text == text) {
    return;
  }
  this.model_.text = text;
  this.updateText();
};

/**
 * Update the comment's view to match the model.
 * @package
 */
Blockly.Comment.prototype.updateText = function() {
  if (this.textarea_) {
    this.textarea_.value = this.model_.text;
  } else if (this.paragraphElement_) {
    // Non-Editable mode.
    // TODO (#2917): If 2917 gets added this will probably need to be updated.
    this.paragraphElement_.firstChild.textContent = this.model_.text;
  }
};

/**
 * Dispose of this comment.
 *
 * If you want to receive a comment "delete" event (newValue: null), then this
 * should not be called directly. Instead call block.setCommentText(null);
 */
Blockly.Comment.prototype.dispose = function() {
  this.block_.comment = null;
  Blockly.Icon.prototype.dispose.call(this);
};

/**
 * CSS for block comment.  See css.js for use.
 */
Blockly.Css.register([
  /* eslint-disable indent */
  '.blocklyCommentTextarea {',
    'background-color: #fef49c;',
    'border: 0;',
    'outline: 0;',
    'margin: 0;',
    'padding: 3px;',
    'resize: none;',
    'display: block;',
    'overflow: hidden;',
  '}'
  /* eslint-enable indent */
]);
<commit_msg>fix #3596:Textarea of comment missing scrollbars (#4447)<commit_after>/**
 * @license
 * Copyright 2011 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @fileoverview Object representing a code comment.
 * @author fraser@google.com (Neil Fraser)
 */
'use strict';

goog.provide('Blockly.Comment');

goog.require('Blockly.Bubble');
goog.require('Blockly.Css');
goog.require('Blockly.Events');
goog.require('Blockly.Events.BlockChange');
goog.require('Blockly.Events.BubbleOpen');
goog.require('Blockly.Icon');
goog.require('Blockly.utils.deprecation');
goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.object');
goog.require('Blockly.utils.Svg');
goog.require('Blockly.utils.userAgent');
goog.require('Blockly.Warning');

/**
 * Class for a comment.
 * @param {!Blockly.Block} block The block associated with this comment.
 * @extends {Blockly.Icon}
 * @constructor
 */
Blockly.Comment = function(block) {
  Blockly.Comment.superClass_.constructor.call(this, block);

  /**
   * The model for this comment.
   * @type {!Blockly.Block.CommentModel}
   * @private
   */
  this.model_ = block.commentModel;
  // If someone creates the comment directly instead of calling
  // block.setCommentText we want to make sure the text is non-null;
  this.model_.text = this.model_.text || '';

  /**
   * The model's text value at the start of an edit.
   * Used to tell if an event should be fired at the end of an edit.
   * @type {?string}
   * @private
   */
  this.cachedText_ = '';

  /**
   * Mouse up event data.
   * @type {?Blockly.EventData}
   * @private
   */
  this.onMouseUpWrapper_ = null;

  /**
   * Wheel event data.
   * @type {?Blockly.EventData}
   * @private
   */
  this.onWheelWrapper_ = null;

  /**
   * Change event data.
   * @type {?Blockly.EventData}
   * @private
   */
  this.onChangeWrapper_ = null;

  /**
   * Input event data.
   * @type {?Blockly.EventData}
   * @private
   */
  this.onInputWrapper_ = null;

  this.createIcon();
};
Blockly.utils.object.inherits(Blockly.Comment, Blockly.Icon);

/**
 * Draw the comment icon.
 * @param {!Element} group The icon group.
 * @protected
 */
Blockly.Comment.prototype.drawIcon_ = function(group) {
  // Circle.
  Blockly.utils.dom.createSvgElement(
      Blockly.utils.Svg.CIRCLE,
      {'class': 'blocklyIconShape', 'r': '8', 'cx': '8', 'cy': '8'},
      group);
  // Can't use a real '?' text character since different browsers and operating
  // systems render it differently.
  // Body of question mark.
  Blockly.utils.dom.createSvgElement(
      Blockly.utils.Svg.PATH,
      {
        'class': 'blocklyIconSymbol',
        'd': 'm6.8,10h2c0.003,-0.617 0.271,-0.962 0.633,-1.266 2.875,-2.405' +
          '0.607,-5.534 -3.765,-3.874v1.7c3.12,-1.657 3.698,0.118 2.336,1.25' +
          '-1.201,0.998 -1.201,1.528 -1.204,2.19z'},
      group);
  // Dot of question mark.
  Blockly.utils.dom.createSvgElement(
      Blockly.utils.Svg.RECT,
      {
        'class': 'blocklyIconSymbol',
        'x': '6.8',
        'y': '10.78',
        'height': '2',
        'width': '2'
      },
      group);
};

/**
 * Create the editor for the comment's bubble.
 * @return {!SVGElement} The top-level node of the editor.
 * @private
 */
Blockly.Comment.prototype.createEditor_ = function() {
  /* Create the editor.  Here's the markup that will be generated in
   * editable mode:
    <foreignObject x="8" y="8" width="164" height="164">
      <body xmlns="http://www.w3.org/1999/xhtml" class="blocklyMinimalBody">
        <textarea xmlns="http://www.w3.org/1999/xhtml"
            class="blocklyCommentTextarea"
            style="height: 164px; width: 164px;"></textarea>
      </body>
    </foreignObject>
   * For non-editable mode see Warning.textToDom_.
   */

  this.foreignObject_ = Blockly.utils.dom.createSvgElement(
      Blockly.utils.Svg.FOREIGNOBJECT,
      {'x': Blockly.Bubble.BORDER_WIDTH, 'y': Blockly.Bubble.BORDER_WIDTH},
      null);

  var body = document.createElementNS(Blockly.utils.dom.HTML_NS, 'body');
  body.setAttribute('xmlns', Blockly.utils.dom.HTML_NS);
  body.className = 'blocklyMinimalBody';

  this.textarea_ = document.createElementNS(
      Blockly.utils.dom.HTML_NS, 'textarea');
  var textarea = this.textarea_;
  textarea.className = 'blocklyCommentTextarea';
  textarea.setAttribute('dir', this.block_.RTL ? 'RTL' : 'LTR');
  textarea.value = this.model_.text;
  this.resizeTextarea_();

  body.appendChild(textarea);
  this.foreignObject_.appendChild(body);

  // Ideally this would be hooked to the focus event for the comment.
  // However doing so in Firefox swallows the cursor for unknown reasons.
  // So this is hooked to mouseup instead.  No big deal.
  this.onMouseUpWrapper_ = Blockly.bindEventWithChecks_(
      textarea, 'mouseup', this, this.startEdit_, true, true);
  // Don't zoom with mousewheel.
  this.onWheelWrapper_ = Blockly.bindEventWithChecks_(
      textarea, 'wheel', this, function(e) {
        e.stopPropagation();
      });
  this.onChangeWrapper_ = Blockly.bindEventWithChecks_(
      textarea, 'change', this, function(_e) {
        if (this.cachedText_ != this.model_.text) {
          Blockly.Events.fire(new Blockly.Events.BlockChange(
              this.block_, 'comment', null, this.cachedText_, this.model_.text));
        }
      });
  this.onInputWrapper_ = Blockly.bindEventWithChecks_(
      textarea, 'input', this, function(_e) {
        this.model_.text = textarea.value;
      });

  setTimeout(textarea.focus.bind(textarea), 0);

  return this.foreignObject_;
};

/**
 * Add or remove editability of the comment.
 * @override
 */
Blockly.Comment.prototype.updateEditable = function() {
  Blockly.Comment.superClass_.updateEditable.call(this);
  if (this.isVisible()) {
    // Recreate the bubble with the correct UI.
    this.disposeBubble_();
    this.createBubble_();
  }
};

/**
 * Callback function triggered when the bubble has resized.
 * Resize the text area accordingly.
 * @private
 */
Blockly.Comment.prototype.onBubbleResize_ = function() {
  if (!this.isVisible()) {
    return;
  }
  this.model_.size = this.bubble_.getBubbleSize();
  this.resizeTextarea_();
};

/**
 * Resizes the text area to match the size defined on the model (which is
 * the size of the bubble).
 * @private
 */
Blockly.Comment.prototype.resizeTextarea_ = function() {
  var size = this.model_.size;
  var doubleBorderWidth = 2 * Blockly.Bubble.BORDER_WIDTH;
  var widthMinusBorder = size.width - doubleBorderWidth;
  var heightMinusBorder = size.height - doubleBorderWidth;
  this.foreignObject_.setAttribute('width', widthMinusBorder);
  this.foreignObject_.setAttribute('height', heightMinusBorder);
  this.textarea_.style.width = (widthMinusBorder - 4) + 'px';
  this.textarea_.style.height = (heightMinusBorder - 4) + 'px';
};

/**
 * Show or hide the comment bubble.
 * @param {boolean} visible True if the bubble should be visible.
 */
Blockly.Comment.prototype.setVisible = function(visible) {
  if (visible == this.isVisible()) {
    return;
  }
  Blockly.Events.fire(
      new Blockly.Events.BubbleOpen(this.block_, visible, 'comment'));
  this.model_.pinned = visible;
  if (visible) {
    this.createBubble_();
  } else {
    this.disposeBubble_();
  }
};

/**
 * Show the bubble. Handles deciding if it should be editable or not.
 * @private
 */
Blockly.Comment.prototype.createBubble_ = function() {
  if (!this.block_.isEditable() || Blockly.utils.userAgent.IE) {
    // Steal the code from warnings to make an uneditable text bubble.
    // MSIE does not support foreignobject; textareas are impossible.
    // https://docs.microsoft.com/en-us/openspecs/ie_standards/ms-svg/56e6e04c-7c8c-44dd-8100-bd745ee42034
    // Always treat comments in IE as uneditable.
    this.createNonEditableBubble_();
  } else {
    this.createEditableBubble_();
  }
};

/**
 * Show an editable bubble.
 * @private
 */
Blockly.Comment.prototype.createEditableBubble_ = function() {
  this.bubble_ = new Blockly.Bubble(
      /** @type {!Blockly.WorkspaceSvg} */ (this.block_.workspace),
      this.createEditor_(), this.block_.pathObject.svgPath,
      /** @type {!Blockly.utils.Coordinate} */ (this.iconXY_),
      this.model_.size.width, this.model_.size.height);
  // Expose this comment's block's ID on its top-level SVG group.
  this.bubble_.setSvgId(this.block_.id);
  this.bubble_.registerResizeEvent(this.onBubbleResize_.bind(this));
  this.applyColour();
};

/**
 * Show a non-editable bubble.
 * @private
 * @suppress {checkTypes} Suppress `this` type mismatch.
 */
Blockly.Comment.prototype.createNonEditableBubble_ = function() {
  // TODO (#2917): It would be great if the comment could support line breaks.
  Blockly.Warning.prototype.createBubble.call(this);
};

/**
 * Dispose of the bubble.
 * @private
 * @suppress {checkTypes} Suppress `this` type mismatch.
 */
Blockly.Comment.prototype.disposeBubble_ = function() {
  if (this.paragraphElement_) {
    // We're using the warning UI so we have to let it dispose.
    Blockly.Warning.prototype.disposeBubble.call(this);
    return;
  }
  if (this.onMouseUpWrapper_) {
    Blockly.unbindEvent_(this.onMouseUpWrapper_);
    this.onMouseUpWrapper_ = null;
  }
  if (this.onWheelWrapper_) {
    Blockly.unbindEvent_(this.onWheelWrapper_);
    this.onWheelWrapper_ = null;
  }
  if (this.onChangeWrapper_) {
    Blockly.unbindEvent_(this.onChangeWrapper_);
    this.onChangeWrapper_ = null;
  }
  if (this.onInputWrapper_) {
    Blockly.unbindEvent_(this.onInputWrapper_);
    this.onInputWrapper_ = null;
  }
  this.bubble_.dispose();
  this.bubble_ = null;
  this.textarea_ = null;
  this.foreignObject_ = null;
};

/**
 * Callback fired when an edit starts.
 *
 * Bring the comment to the top of the stack when clicked on. Also cache the
 * current text so it can be used to fire a change event.
 * @param {!Event} _e Mouse up event.
 * @private
 */
Blockly.Comment.prototype.startEdit_ = function(_e) {
  if (this.bubble_.promote()) {
    // Since the act of moving this node within the DOM causes a loss of focus,
    // we need to reapply the focus.
    this.textarea_.focus();
  }

  this.cachedText_ = this.model_.text;
};

/**
 * Get the dimensions of this comment's bubble.
 * @return {Blockly.utils.Size} Object with width and height properties.
 */
Blockly.Comment.prototype.getBubbleSize = function() {
  return this.model_.size;
};

/**
 * Size this comment's bubble.
 * @param {number} width Width of the bubble.
 * @param {number} height Height of the bubble.
 */
Blockly.Comment.prototype.setBubbleSize = function(width, height) {
  if (this.bubble_) {
    this.bubble_.setBubbleSize(width, height);
  } else {
    this.model_.size.width = width;
    this.model_.size.height = height;
  }
};

/**
 * Returns this comment's text.
 * @return {string} Comment text.
 * @deprecated August 2019 Use block.getCommentText() instead.
 */
Blockly.Comment.prototype.getText = function() {
  Blockly.utils.deprecation.warn(
      'Comment.prototype.getText',
      'August 2019',
      'December 2020',
      'block.getCommentText');
  return this.model_.text || '';
};

/**
 * Set this comment's text.
 *
 * If you want to receive a comment change event, then this should not be called
 * directly. Instead call block.setCommentText();
 * @param {string} text Comment text.
 * @deprecated August 2019 Use block.setCommentText() instead.
 */
Blockly.Comment.prototype.setText = function(text) {
  Blockly.utils.deprecation.warn(
      'Comment.prototype.setText',
      'August 2019',
      'December 2020',
      'block.setCommentText');
  if (this.model_.text == text) {
    return;
  }
  this.model_.text = text;
  this.updateText();
};

/**
 * Update the comment's view to match the model.
 * @package
 */
Blockly.Comment.prototype.updateText = function() {
  if (this.textarea_) {
    this.textarea_.value = this.model_.text;
  } else if (this.paragraphElement_) {
    // Non-Editable mode.
    // TODO (#2917): If 2917 gets added this will probably need to be updated.
    this.paragraphElement_.firstChild.textContent = this.model_.text;
  }
};

/**
 * Dispose of this comment.
 *
 * If you want to receive a comment "delete" event (newValue: null), then this
 * should not be called directly. Instead call block.setCommentText(null);
 */
Blockly.Comment.prototype.dispose = function() {
  this.block_.comment = null;
  Blockly.Icon.prototype.dispose.call(this);
};

/**
 * CSS for block comment.  See css.js for use.
 */
Blockly.Css.register([
  /* eslint-disable indent */
  '.blocklyCommentTextarea {',
    'background-color: #fef49c;',
    'border: 0;',
    'outline: 0;',
    'margin: 0;',
    'padding: 3px;',
    'resize: none;',
    'display: block;',
    'text-overflow: hidden;',
  '}'
  /* eslint-enable indent */
]);
<|endoftext|>


<commit_before>CLASS({
  name: 'Controller',
  properties: [
    { name: 'search', view: { factory_: 'TextFieldView', onKeyMode: true } },
    { name: 'dao', defaultValue: questions },
    {
      name: 'filteredDAO',
      model_: 'DAOProperty',
      view: { factory_: 'DAOListView', mode: 'read-only' },
      dynamicValue: function() {
        return this.dao.where(CONTAINS_IC(SEQ(
          Question.TITLE,
          Question.QUESTION,
          Question.LABELS,
          Question.ANSWER), this.search));
      }
    }
  ],
  methods: {
    init: function() {
      this.SUPER();
      var i = window.location.href.indexOf('?q=');
      if ( i != -1 ) this.search = window.location.href.substring(i+3);
    }
  },
  templates: [
    function CSS() {/*
      code {
        background: #eee;
        display: inline-block;
        margin-left: 30px;
        padding: 16px;
      }
    */},
    function toDetailHTML() {/*

FOAM-Overflow

        Ask Question
           Search: $$search

        <foam f="filteredDAO" className="questions" tagName="div">

            Q$$id{mode: 'read-only'}. $$title{mode: 'read-only'}
            <% if ( this.data.question.trim().length ) { %>
$$question{mode: 'read-only', escapeHTML: false}
<% } %>

            A.
$$answer{mode: 'read-only', escapeHTML: false}

            labels: <%= this.data.labels.join(', ') %>

            <% if ( this.data.src ) { %>
              Source: here

            <% } %>

        </foam>
    */}
  ]
});

/*
$$id{mode: 'read-only'}
*/
<commit_msg>Overflow: Improved template.<commit_after>CLASS({
  name: 'Controller',
  properties: [
    { name: 'search', view: { factory_: 'TextFieldView', onKeyMode: true } },
    { name: 'dao', defaultValue: questions },
    {
      name: 'filteredDAO',
      model_: 'DAOProperty',
      view: { factory_: 'DAOListView', mode: 'read-only' },
      dynamicValue: function() {
        return this.dao.where(CONTAINS_IC(SEQ(
          Question.TITLE,
          Question.QUESTION,
          Question.LABELS,
          Question.ANSWER), this.search));
      }
    }
  ],
  methods: {
    init: function() {
      this.SUPER();
      var i = window.location.href.indexOf('?q=');
      if ( i != -1 ) this.search = window.location.href.substring(i+3);
    }
  },
  templates: [
    function CSS() {/*
      code {
        background: #eee;
        display: inline-block;
        margin-left: 30px;
        padding: 16px;
        padding-top: 0;
      }
    */},
    function toDetailHTML() {/*

FOAM-Overflow

        Ask Question
           Search: $$search

        <foam f="filteredDAO" className="questions" tagName="div">

            Q$$id{mode: 'read-only'}. $$title{mode: 'read-only'}
            <% if ( this.data.question.length ) { %>
$$question{mode: 'read-only', escapeHTML: false}
<% } else { %>
 <% } %>
            A.
$$answer{mode: 'read-only', escapeHTML: false}

            labels: <%= this.data.labels.join(', ') %>

            <% if ( this.data.src ) { %>
              Source: here

            <% } %>

        </foam>
    */}
  ]
});

/*
$$id{mode: 'read-only'}
*/
<|endoftext|>


<commit_before>// Copyright 2016, University of Colorado Boulder

/**
 * Generates the PhET-iO API Type documentation which will be shown at https://phet-io.colorado.edu/devguide/
 *
 * @author Michael Kauzmann (PhET Interactive Simulations)
 * @author Sam Reid (PhET Interactive Simulations)
 */
/* eslint-env node */
'use strict';
var requirejs = require( '../../../node_modules/requirejs/bin/r.js' ); // eslint-disable-line require-statement-match
var fs = require( 'fs' );

// Mock up window globals for running in node mode

global.assert = require( 'assert' );
global.assert = null;
global._ = require( '../../../sherpa/lib/lodash-4.17.4.js' );
global.phet = {
  chipper: {
    queryParameters: {},
    brand: 'phet-io'
  },
  phetio: {
    queryParameters: {
      phetioExpressions: '[]'
    }
  }
};

global.window = global; // eslint-disable-line no-native-reassign

var assert = global.assert;

requirejs.config( {
  paths: {
    text: '../../../sherpa/lib/text-2.0.12',
    ifphetio: '../../../chipper/js/requirejs-plugins/ifphetio',

    AXON: '../../../axon/js',
    DOT: '../../../dot/js',
    JOIST: '../../../joist/js',
    KITE: '../../../kite/js',
    PHETCOMMON: '../../../phetcommon/js',
    REPOSITORY: '../..',
    PHET_CORE: '../../../phet-core/js',
    PHET_IO: '../../../phet-io/js',
    SHRED: '../../../shred/js',
    SCENERY: '../../../scenery/js',
    SCENERY_PHET: '../../../scenery-phet/js',
    SUN: '../../../sun/js',
    TANDEM: '../../../tandem/js',
    VEGAS: '../../../vegas/js',
    VIBE: '../../../vibe/js'

    // Not sure how to handle individual simulations' documentation
    // BALLOONS_AND_STATIC_ELECTRICITY: '../../../balloons-and-static-electricity/js',
    // BEERS_LAW_LAB: '../../../beers-law-lab/js',
    // BUILD_AN_ATOM: '../../../build-an-atom/js',
    // CHARGES_AND_FIELDS: '../../../charges-and-fields/js',
    // COLOR_VISION: '../../../color-vision/js',
  }
} );

var TObject = requirejs( 'PHET_IO/types/TObject' );

// Get a list of all of the types in the 'types' directory
var walkSync = function( dir, filelist ) {
  var files = fs.readdirSync( dir );
  filelist = filelist || [];
  files.forEach( function( file ) {
    if ( fs.statSync( dir + '/' + file ).isDirectory() ) {
      filelist = walkSync( dir + '/' + file, filelist );
    }
    else {
      if ( file.match( /^T[A-Z].*\.js/ ) ) {
        filelist.push( dir + '/' + file.replace( '.js', '' ) );
      }
    }
  } );
  return filelist;
};

var findFiles = function() {
  var activeRepos = fs.readFileSync( '../../../chipper/data/active-repos' ).toString();
  activeRepos = activeRepos.split( '\r\n' );

  var activeSims = fs.readFileSync( '../../../chipper/data/active-sims' ).toString();
  activeSims = activeSims.split( '\r\n' );

  // Repos that we don't want to search because they have no js/ directories, and won't have TTypes
  var blackList = [ 'babel', 'exemplar', 'function-basics', 'phet-info', 'phet-io-website', 'phet-ios-app',
    'phet-android-app', 'phet-cafepress', 'sherpa', 'slater', 'tambo', 'tasks', 'yotta', 'scenery' ];

  // TODO: this is a hack, we want these repos, but not the error
  // These are common code repos that throw a 'document is not defined error' but have files that need to be entered.
  blackList = blackList.concat( [ 'scenery-phet', 'sun', 'joist' ] );
  blackList.forEach( function( repo ) {
    activeRepos.splice( activeRepos.indexOf( repo ), 1 );
  } );

  activeSims.forEach( function( repo ) {
    activeRepos.splice( activeRepos.indexOf( repo ), 1 );
  } );

  var commonRepos = activeRepos;

  var files = [];
  commonRepos.forEach( function( repoName ) {
    walkSync( '../../../' + repoName + '/js', files );
  } );

  return files;

};

var toHTML = function( json ) {
  var html = '';
  var types = _.sortBy( _.keys( json ), function( key ) {
    return key;
  } );
  for ( var i = 0; i < types.length; i++ ) {

    var typeName = types[ i ];
    var wrapperType = json[ typeName ];
    var methods = '';
    var sortedMethodNames = _.sortBy( _.keys( wrapperType.methods ), function( key ) {
      return key;
    } );
    for ( var k = 0; k < sortedMethodNames.length; k++ ) {
      var methodName = sortedMethodNames[ k ];
      var method = wrapperType.methods[ methodName ];
      var params = method.parameterTypes.map( function( p ) {
        assert && assert( p.typeName, 'Parameter was not a type in toHTML: ' + typeName + '.' + methodName );
        return p.typeName;
      } );
      methods += '
' + methodName + ': (' + params + ') ➞ ' + method.returnType.typeName + '
' +
                 '
' + method.documentation + '
';
    }
    var eventsString = wrapperType.events ? 'events: ' + ( wrapperType.events || '' ) + '' : '';

    var supertype = wrapperType.supertype;
    html = html + '
' + typeName + ' ' +
           (supertype.typeName ? '(extends ' + supertype.typeName + ')' : '') +
           '
' +
           '
' +
           '' + wrapperType.documentation + '
' +
           eventsString +
           '
' + methods + '

';
  }
  return html;
};

module.exports = function( callback ) {
  var allFiles = findFiles();

  /*
  These are problematic phet-io imports, need to investigate more. It seems like requirejs is just stopping and not
  calling the callback that we give them. There aren't any errors though:

  // TODO: Why the heck do these files make the program die silently on requirejs import?
   '../../../phet-io/js/types/TString',
   '../../../phet-io/js/types/TPhETIO',
   '../../../phet-io/js/types/TFunctionWrapper',
   '../../../phet-io/js/types/TVoid',

   */

  // These are gathered dynamically above, but to test I manually removed the files that make it die silently
  allFiles = [
    '../../../phet-io/js/types/TArray',
    '../../../phet-io/js/types/TBoolean',
    '../../../phet-io/js/types/TNumber',
    '../../../phet-io/js/types/TSimIFrameAPI',
    '../../../phet-io/js/types/TObject',

    '../../../axon/js/TDerivedProperty',
    '../../../axon/js/TEvents',
    '../../../axon/js/TObservableArray',
    '../../../axon/js/TProperty',
    '../../../dot/js/TBounds2',
    '../../../dot/js/TBounds3',
    '../../../dot/js/TRandom',
    '../../../dot/js/TVector2',
    '../../../dot/js/TVector3',
    '../../../phetcommon/js/model/TSphereBucket',

    '../../../shred/js/model/TParticle',
    '../../../shred/js/model/TParticleAtom',
    '../../../shred/js/view/TPeriodicTableCell',
    '../../../tandem/js/axon/TTandemEmitter',
    '../../../tandem/js/scenery/input/TTandemSimpleDragHandler',
    '../../../tandem/js/TTandem' ];

  console.log( allFiles );
  var result = {};

  requirejs( allFiles, function( /* arguments */ ) {
    console.log( 'in require callback');
    var args = Array.prototype.slice.call( arguments );
    for ( var i = 0; i < args.length; i++ ) {
      var arg = args[ i ];
      var filename = allFiles[ i ];
      if ( arg.typeName ) {
        result[ arg.typeName ] = arg;
      }
      else {
        if ( filename.indexOf( 'TFunctionWrapper' ) >= 0 ) {
          var returnType = TObject;
          var parameterTypes = [ TObject ];
          var instantiatedType = arg( returnType, parameterTypes );
          result[ instantiatedType.typeName ] = instantiatedType;
        }
        else {
          var instanceType = arg( TObject );
          result[ instanceType.typeName ] = instanceType;
        }
      }
    }

    var html = toHTML( result );
    callback( html );
  } );
};

// Called from the command line, and not required
if ( require.main === module ) {
  module.exports( function( html ) { console.log( html ); } );
}
<commit_msg>fixed bugs to get Ttypes to load in node, https://github.com/phetsims/phet-io/issues/972<commit_after>// Copyright 2016, University of Colorado Boulder

/**
 * Generates the PhET-iO API Type documentation which will be shown at https://phet-io.colorado.edu/devguide/
 *
 * @author Michael Kauzmann (PhET Interactive Simulations)
 * @author Sam Reid (PhET Interactive Simulations)
 */
/* eslint-env node */
'use strict';
var requirejs = require( '../../../node_modules/requirejs/bin/r.js' ); // eslint-disable-line require-statement-match
var fs = require( 'fs' );

// Mock up window globals for running in node mode

global.assert = require( 'assert' );
global._ = require( '../../../sherpa/lib/lodash-4.17.4.js' );
global.phet = {
  chipper: {
    queryParameters: {},
    brand: 'phet-io'
  },
  phetio: {
    queryParameters: {
      phetioExpressions: '[]'
    }
  }
};

// This is a hack because we are now loading scenery.js as a namespace.
global.document = { createElement: function() {return { getContext: function() {} };} };

global.window = global; // eslint-disable-line no-native-reassign

var assert = global.assert;

requirejs.config( {
  paths: {
    text: '../../../sherpa/lib/text-2.0.12',
    ifphetio: '../../../chipper/js/requirejs-plugins/ifphetio',

    AXON: '../../../axon/js',
    DOT: '../../../dot/js',
    JOIST: '../../../joist/js',
    KITE: '../../../kite/js',
    PHETCOMMON: '../../../phetcommon/js',
    REPOSITORY: '../..',
    PHET_CORE: '../../../phet-core/js',
    PHET_IO: '../../../phet-io/js',
    SHRED: '../../../shred/js',
    SCENERY: '../../../scenery/js',
    SCENERY_PHET: '../../../scenery-phet/js',
    SUN: '../../../sun/js',
    TANDEM: '../../../tandem/js',
    VEGAS: '../../../vegas/js',
    VIBE: '../../../vibe/js'

  }
} );

var TObject = requirejs( 'PHET_IO/types/TObject' );

// Get a list of all of the types in the 'types' directory
var walkSync = function( dir, filelist ) {
  var files = fs.readdirSync( dir );
  filelist = filelist || [];
  files.forEach( function( file ) {
    if ( fs.statSync( dir + '/' + file ).isDirectory() ) {
      filelist = walkSync( dir + '/' + file, filelist );
    }
    else {
      if ( file.match( /^T[A-Z].*\.js/ ) ) {
        filelist.push( dir + '/' + file.replace( '.js', '' ) );
      }
    }
  } );
  return filelist;
};

var findFiles = function() {
  var activeRepos = fs.readFileSync( '../../../chipper/data/active-repos' ).toString();
  activeRepos = activeRepos.split( '\r\n' );

  var activeSims = fs.readFileSync( '../../../chipper/data/active-sims' ).toString();
  activeSims = activeSims.split( '\r\n' );

  // Repos that we don't want to search because they have no js/ directories, and won't have TTypes
  var blackList = [ 'babel', 'exemplar', 'function-basics', 'phet-info', 'phet-io-website', 'phet-ios-app',
    'phet-android-app', 'phet-cafepress', 'sherpa', 'slater', 'tambo', 'tasks', 'yotta' ];

  blackList.forEach( function( repo ) {
    activeRepos.splice( activeRepos.indexOf( repo ), 1 );
  } );

  activeSims.forEach( function( repo ) {
    activeRepos.splice( activeRepos.indexOf( repo ), 1 );
  } );

  var commonRepos = activeRepos;

  var files = [];
  commonRepos.forEach( function( repoName ) {
    walkSync( '../../../' + repoName + '/js', files );
  } );

  return files;

};

var toHTML = function( json ) {
  var html = '';
  var types = _.sortBy( _.keys( json ), function( key ) {
    return key;
  } );
  for ( var i = 0; i < types.length; i++ ) {

    var typeName = types[ i ];
    var wrapperType = json[ typeName ];
    var methods = '';
    var sortedMethodNames = _.sortBy( _.keys( wrapperType.methods ), function( key ) {
      return key;
    } );
    for ( var k = 0; k < sortedMethodNames.length; k++ ) {
      var methodName = sortedMethodNames[ k ];
      var method = wrapperType.methods[ methodName ];
      var params = method.parameterTypes.map( function( p ) {
        assert && assert( p.typeName, 'Parameter was not a type in toHTML: ' + typeName + '.' + methodName );
        return p.typeName;
      } );
      methods += '
' + methodName + ': (' + params + ') ➞ ' + method.returnType.typeName + '
' +
                 '
' + method.documentation + '
';
    }
    var eventsString = wrapperType.events ? 'events: ' + ( wrapperType.events || '' ) + '' : '';

    var supertype = wrapperType.supertype;
    html = html + '
' + typeName + ' ' +
           (supertype.typeName ? '(extends ' + supertype.typeName + ')' : '') +
           '
' +
           '
' +
           '' + wrapperType.documentation + '
' +
           eventsString +
           '
' + methods + '

';
  }
  return html;
};

module.exports = function( callback ) {
  var allFiles = findFiles();

  console.log( allFiles );
  var result = {};

  // Preload all of the files.  This is a hack workaround that somehow allows the batch requirejs to proceed.
  for ( var i = 0; i < allFiles.length; i++ ) {
    var filename = allFiles[ i ];

    global.phet = {
      chipper: {
        queryParameters: {},
        brand: 'phet-io'
      },
      phetio: {
        queryParameters: {
          phetioExpressions: '[]'
        }
      }
    };

    console.log( 'hello: ' + filename );
    var arg = requirejs( filename );
    if ( arg.typeName ) {
      result[ arg.typeName ] = arg;
    }
    else {
      if ( filename.indexOf( 'TFunctionWrapper' ) >= 0 ) {
        var returnType = TObject;
        var parameterTypes = [ TObject ];
        var instantiatedType = arg( returnType, parameterTypes );
        result[ instantiatedType.typeName ] = instantiatedType;
      }
      else {
        var instanceType = arg( TObject );
        result[ instanceType.typeName ] = instanceType;
      }
    }
  }
  var html = toHTML( result );
  callback( html );
};

// Called from the command line, and not required
if ( require.main === module ) {
  module.exports( function( html ) { console.log( html ); } );
}
<|endoftext|>


<commit_before>module.exports = {
  'Does Chat Exist' : function (browser) {
    browser
      .url(browser.launchUrl)
      .waitForElementVisible('body', 1000)
      .waitForElementVisible('iframe#drift-widget', 1000)
      .frame('iframe#drift-widget')
      .click('iframe#drift-widget')
      .end();
  }
};<commit_msg>Create test for make chat open<commit_after>module.exports = {
  'Does Chat Exist' : function (browser) {
    browser
      .url(browser.launchUrl)
      .waitForElementVisible('body', 1000)
      .waitForElementVisible('iframe#drift-widget', 1000)
      .frame('drift-widget')
      .click('button.CHAT')
      .end();
  },
  'Does Chat Open' : function (browser) {
    browser
      .url(browser.launchUrl)
      .waitForElementVisible('body', 1000)
      .waitForElementVisible('iframe#drift-widget', 1000)
      .frame('drift-widget')
      .click('button.CHAT')
      .pause(1000)
      .verify.elementPresent('div._1b8HzQM8nmpP_VSMq7OnIz')
      .end();
  }
};<|endoftext|>


<commit_before><commit_msg>Remove PR, AK, and HI for now<commit_after><|endoftext|>


<commit_before>/*
 *  test_promise_add.js
 *
 *  David Janes
 *  IOTDB
 *  2017-11-17
 */

"use strict";

const _ = require("..")
const assert = require("assert")

describe("promise/done", function() {
    describe("good", function() {
        const original = {
            "value": 10,
            "json": {
                "value": 15,
            },
        }

        it("sd", function(done) {
            const my_done = (error, self) => {
                assert.deepEqual(self.value, original.value)
                assert.deepEqual(self.json, original.json)

                done(error)
            }

            _.promise.make(original)
                .then(_.promise.done(my_done))
                .catch(done)
        })
        it("sd with change", function(done) {
            const my_done = (error, self) => {
                assert.deepEqual(self.value, 20)
                assert.deepEqual(self.json, original.json)

                done(error)
            }

            _.promise.make(original)
                .then(_.promise.add("value", 20))
                .then(_.promise.done(my_done))
                .catch(done)
        })
        it("self", function(done) {
            const my_done = (error, self) => {
                assert.deepEqual(self.value, original.value)
                assert.deepEqual(self.json, original.json)

                done(error)
            }

            _.promise.make(original)
                .then(_.promise.done(my_done, original))
                .catch(done)
        })
        it("self with change", function(done) {
            const my_done = (error, self) => {
                assert.deepEqual(self.value, original.value)
                assert.deepEqual(self.json, original.json)

                done(error)
            }

            _.promise.make(original)
                .then(_.promise.add("value", 20))
                .then(_.promise.done(my_done, original))
                .catch(done)
        })
        it("self with variable", function(done) {
            const my_done = (error, self) => {
                assert.deepEqual(self.value, 30); // original.value)
                assert.deepEqual(self.json, original.json)

                done(error)
            }

            _.promise.make(original)
                .then(_.promise.add("value", 30))
                .then(_.promise.done(my_done, original, "value"))
                .catch(done)
        })
        it("self with nested variable", function(done) {
            const my_done = (error, self) => {
                assert.deepEqual(self.value, original.json.value)
                assert.deepEqual(self.json, original.json)

                done(error)
            }

            _.promise.make(original)
                .then(_.promise.add("value", 30))
                .then(_.promise.done(my_done, original, "json/value"))
                .catch(done)
        })
        it("self with nested variable and rename", function(done) {
            const my_done = (error, self) => {
                assert.deepEqual(self.value, original.value)
                assert.deepEqual(self.v2, original.json.value)
                assert.deepEqual(self.json, original.json)

                done(error)
            }

            _.promise.make(original)
                .then(_.promise.add("value", 30))
                .then(_.promise.done(my_done, original, "json/value:v2"))
                .catch(done)
        })
        it("self with multiple arguments", function(done) {
            const my_done = (error, self) => {
                assert.deepEqual(self.value, original.value)
                assert.deepEqual(self.nvalue, original.value)
                assert.deepEqual(self.json, original.json)
                assert.deepEqual(self.njson, original.json)

                done(error)
            }

            _.promise.make(original)
                .then(_.promise.done(my_done, original, "json:njson", "value:nvalue"))
                .catch(done)
        })
        it("self with comma arguments", function(done) {
            const my_done = (error, self) => {
                assert.deepEqual(self.value, original.value)
                assert.deepEqual(self.nvalue, original.value)
                assert.deepEqual(self.json, original.json)
                assert.deepEqual(self.njson, original.json)

                done(error)
            }

            _.promise.make(original)
                .then(_.promise.done(my_done, original, "json:njson,value:nvalue"))
                .catch(done)
        })
    })
})
<commit_msg>expand test cases<commit_after>/*
 *  test_promise_add.js
 *
 *  David Janes
 *  IOTDB
 *  2017-11-17
 */

"use strict";

const _ = require("..")
const assert = require("assert")

describe("promise/done", function() {
    const original = {
        "value": 10,
        "json": {
            "value": 15,
        },
    }

    describe("old-way", function() {
        it("sd", function(done) {
            const my_done = (error, self) => {
                assert.deepEqual(self.value, original.value)
                assert.deepEqual(self.json, original.json)

                done(error)
            }

            _.promise.make(original)
                .then(_.promise.done(my_done))
                .catch(done)
        })
        it("sd with change", function(done) {
            const my_done = (error, self) => {
                assert.deepEqual(self.value, 20)
                assert.deepEqual(self.json, original.json)

                done(error)
            }

            _.promise.make(original)
                .then(_.promise.add("value", 20))
                .then(_.promise.done(my_done))
                .catch(done)
        })
        it("self", function(done) {
            const my_done = (error, self) => {
                assert.deepEqual(self.value, original.value)
                assert.deepEqual(self.json, original.json)

                done(error)
            }

            _.promise.make(original)
                .then(_.promise.done(my_done, original))
                .catch(done)
        })
        it("self with change", function(done) {
            const my_done = (error, self) => {
                assert.deepEqual(self.value, original.value)
                assert.deepEqual(self.json, original.json)

                done(error)
            }

            _.promise.make(original)
                .then(_.promise.add("value", 20))
                .then(_.promise.done(my_done, original))
                .catch(done)
        })
        it("self with variable", function(done) {
            const my_done = (error, self) => {
                assert.deepEqual(self.value, 30); // original.value)
                assert.deepEqual(self.json, original.json)

                done(error)
            }

            _.promise.make(original)
                .then(_.promise.add("value", 30))
                .then(_.promise.done(my_done, original, "value"))
                .catch(done)
        })
        it("self with nested variable", function(done) {
            const my_done = (error, self) => {
                assert.deepEqual(self.value, original.json.value)
                assert.deepEqual(self.json, original.json)

                done(error)
            }

            _.promise.make(original)
                .then(_.promise.add("value", 30))
                .then(_.promise.done(my_done, original, "json/value"))
                .catch(done)
        })
        it("self with nested variable and rename", function(done) {
            const my_done = (error, self) => {
                assert.deepEqual(self.value, original.value)
                assert.deepEqual(self.v2, original.json.value)
                assert.deepEqual(self.json, original.json)

                done(error)
            }

            _.promise.make(original)
                .then(_.promise.add("value", 30))
                .then(_.promise.done(my_done, original, "json/value:v2"))
                .catch(done)
        })
        it("self with multiple arguments", function(done) {
            const my_done = (error, self) => {
                assert.deepEqual(self.value, original.value)
                assert.deepEqual(self.nvalue, original.value)
                assert.deepEqual(self.json, original.json)
                assert.deepEqual(self.njson, original.json)

                done(error)
            }

            _.promise.make(original)
                .then(_.promise.done(my_done, original, "json:njson", "value:nvalue"))
                .catch(done)
        })
        it("self with comma arguments", function(done) {
            const my_done = (error, self) => {
                assert.deepEqual(self.value, original.value)
                assert.deepEqual(self.nvalue, original.value)
                assert.deepEqual(self.json, original.json)
                assert.deepEqual(self.njson, original.json)

                done(error)
            }

            _.promise.make(original)
                .then(_.promise.done(my_done, original, "json:njson,value:nvalue"))
                .catch(done)
        })
    })
    describe("new-way", function() {
        it("sd", function(done) {
            const my_done = (error, self) => {
                assert.deepEqual(self.value, original.value)
                assert.deepEqual(self.json, original.json)

                done(error)
            }

            _.promise(original)
                .end(my_done)
        })
        it("sd with change", function(done) {
            const my_done = (error, self) => {
                assert.deepEqual(self.value, 20)
                assert.deepEqual(self.json, original.json)

                done(error)
            }

            _.promise(original)
                .add("value", 20)
                .end(my_done)
        })
        it("self", function(done) {
            const my_done = (error, self) => {
                assert.deepEqual(self.value, original.value)
                assert.deepEqual(self.json, original.json)

                done(error)
            }

            _.promise(original)
                .end(my_done, original)
        })
        it("self with change", function(done) {
            const my_done = (error, self) => {
                assert.deepEqual(self.value, original.value)
                assert.deepEqual(self.json, original.json)

                done(error)
            }

            _.promise(original)
                .add("value", 20)
                .end(my_done, original)
        })
        it("self with variable", function(done) {
            const my_done = (error, self) => {
                assert.deepEqual(self.value, 30); // original.value)
                assert.deepEqual(self.json, original.json)

                done(error)
            }

            _.promise(original)
                .add("value", 30)
                .end(my_done, original, "value")
        })
        it("self with nested variable", function(done) {
            const my_done = (error, self) => {
                assert.deepEqual(self.value, original.json.value)
                assert.deepEqual(self.json, original.json)

                done(error)
            }

            _.promise(original)
                .add("value", 30)
                .end(my_done, original, "json/value")
        })
        it("self with nested variable and rename", function(done) {
            const my_done = (error, self) => {
                assert.deepEqual(self.value, original.value)
                assert.deepEqual(self.v2, original.json.value)
                assert.deepEqual(self.json, original.json)

                done(error)
            }

            _.promise(original)
                .add("value", 30)
                .end(my_done, original, "json/value:v2")
        })
        it("self with multiple arguments", function(done) {
            const my_done = (error, self) => {
                assert.deepEqual(self.value, original.value)
                assert.deepEqual(self.nvalue, original.value)
                assert.deepEqual(self.json, original.json)
                assert.deepEqual(self.njson, original.json)

                done(error)
            }

            _.promise(original)
                .end(my_done, original, "json:njson", "value:nvalue")
        })
        it("self with comma arguments", function(done) {
            const my_done = (error, self) => {
                assert.deepEqual(self.value, original.value)
                assert.deepEqual(self.nvalue, original.value)
                assert.deepEqual(self.json, original.json)
                assert.deepEqual(self.njson, original.json)

                done(error)
            }

            _.promise(original)
                .end(my_done, original, "json:njson,value:nvalue")
        })
    })
})
<|endoftext|>


<commit_before>/* eslint no-console: 0 */

const cheerio = require('cheerio');
const request = require('superagent');
const moment = require('moment');

const config = require('./../config');
const omdbCache = require('./omdbCache');
const detailCache = require('./detailCache');
const { gregorianToEthiopic, gregorianWeekdayToEthiopicWeekday, ethiopicMonthToFullEthiopicMonth } = require('./ec');

/**
 * a (get it) scraper targeting http://ednamall.co/
 */
module.exports = moedoo => () => new Promise((resolve, reject) => {
  request
    .get(encodeURI(decodeURI(config.EDNA_URL)))
    .then((res) => {
      // loading the body in cheerio...
      const $ = cheerio.load(res.text);

      /**
       * First h3 tag under section#main contains todays days in YYYY-MM-YY format
       * IF we get an array of length 3 the system is in accordance with our scrapper
       */
      const [year, month, day] = $('body section#main header > h3')
        .text()
        .trim()
        .match(/\d{2,4}/g)
        .map(p => Number(p));
      const gc = moment(`${year}-${month}-${day}`, 'YYYY-M-D');
      const ec = gregorianToEthiopic(year, month, day);
      const moviesDetail = Object.create(null); // this will hold movie detail pages...
      const moviesTitle = Object.create(null); // this will hold movie titles...

      /**
       * first three div.row contain cinema 3, 2 & 1 respectively
       */
      const [c3, c2, c1] = $('body section#main div.row').splice(0, 3).map(cinema =>
        $('div', cinema).splice(1).map((div) => {
          const detail = `${config.EDNA_URL}${$('a', div).attr('href')}`;
          const title = $('a', div).text().trim();
          moviesDetail[detail] = detail;
          moviesTitle[title] = title;

          return {
            title,
            poster: $('img', div).attr('src'),
            time: $('ul', div).text().trim(),
            detail,
          };
        })
      );

      // going through details page and getting videos (if any)...
      // $('iframe[src*="youtube"]') | ('iframe[src*="imdb"]')
      const moviesDetailKeys = Object.keys(moviesDetail);
      const moviesDetailKeysPromises = moviesDetailKeys.map(detail => detailCache(moedoo, detail));

      Promise
        .all(moviesDetailKeysPromises)
        .then((moviesDetailKeysData) => {
          moviesDetailKeysData.forEach((moviesDetailKeyResponse, index) => {
            if (moviesDetailKeyResponse === null || typeof moviesDetailKeyResponse === 'string') {
              moviesDetail[moviesDetailKeys[index]] = moviesDetailKeyResponse;
            } else {
              const youtube = cheerio.load(moviesDetailKeyResponse.text)('iframe[src*="youtube"]');
              const imdb = cheerio.load(moviesDetailKeyResponse.text)('iframe[src*="imdb"]');
              moviesDetail[moviesDetailKeys[index]] = youtube.length > 0 ? youtube.attr('src') : null;
              // checking imdb embed if YouTube is not found...
              if (moviesDetail[moviesDetailKeys[index]] === null) {
                moviesDetail[moviesDetailKeys[index]] = imdb.length > 0 ? imdb.attr('src') : null;
              }

              detailCache(moedoo, moviesDetailKeys[index], moviesDetail[moviesDetailKeys[index]])
                .catch((err) => {
                  console.error(err);
                });
            }
          });

          // hitting omdb to get synopsis...
          const moviesTitleKeys = Object.keys(moviesTitle);
          const moviesTitleKeysPromises = moviesTitleKeys.map(title => omdbCache(moedoo, title));

          Promise
            .all(moviesTitleKeysPromises)
            .then((moviesTitleKeysData) => {
              moviesTitleKeysData.forEach((moviesTitleKeyResponse, index) => {
                if (moviesTitleKeyResponse === null || Object.prototype.hasOwnProperty.call(moviesTitleKeyResponse, 'body') === false) {
                  moviesTitle[moviesTitleKeys[index]] = moviesTitleKeyResponse;
                } else {
                  if (moviesTitleKeyResponse.body.Response === 'False') {
                    moviesTitle[moviesTitleKeys[index]] = null;
                  } else if (
                    moviesTitleKeyResponse.body.Title.toLocaleLowerCase() ===
                    moviesTitle[moviesTitleKeys[index]].toLocaleLowerCase()
                  ) {
                    moviesTitle[moviesTitleKeys[index]] = moviesTitleKeyResponse.body;
                  } else {
                    moviesTitle[moviesTitleKeys[index]] = null;
                  }

                  omdbCache(moedoo, moviesTitleKeys[index], moviesTitle[moviesTitleKeys[index]])
                    .catch((err) => {
                      console.error(err);
                    });
                }
              });

              // filling details...
              // we'll be mutating (fix your face, yes, mutating) initial c3, c2 & c1...
              [c3, c2, c1].forEach((c) => {
                c.forEach((show, index) => {
                  // eslint-disable-next-line
                  c[index].video = moviesDetail[c[index].detail];
                  // eslint-disable-next-line
                  c[index].omdb = moviesTitle[c[index].title];
                });
              });

              resolve({
                show: { c3, c2, c1 },
                meta: {
                  today: `${year}-${month}-${day}`, // YYYY-M-D
                  gc: `${gc.format('dddd, MMMM D')}`, // dddd, MMMM D
                  ec: `${gregorianWeekdayToEthiopicWeekday(gc.format('dddd'))}, ${ethiopicMonthToFullEthiopicMonth(ec.month)}, ${ec.day}`, // dddd, MMMM D
                },
              });
            });
        });
    })
    .catch((err) => {
      reject(err);
    });
});
<commit_msg>comma, Future, Dirty Sprite<commit_after>/* eslint no-console: 0 */

const cheerio = require('cheerio');
const request = require('superagent');
const moment = require('moment');

const config = require('./../config');
const omdbCache = require('./omdbCache');
const detailCache = require('./detailCache');
const { gregorianToEthiopic, gregorianWeekdayToEthiopicWeekday, ethiopicMonthToFullEthiopicMonth } = require('./ec');

/**
 * a (get it) scraper targeting http://ednamall.co/
 */
module.exports = moedoo => () => new Promise((resolve, reject) => {
  request
    .get(encodeURI(decodeURI(config.EDNA_URL)))
    .then((res) => {
      // loading the body in cheerio...
      const $ = cheerio.load(res.text);

      /**
       * First h3 tag under section#main contains todays days in YYYY-MM-YY format
       * IF we get an array of length 3 the system is in accordance with our scrapper
       */
      const [year, month, day] = $('body section#main header > h3')
        .text()
        .trim()
        .match(/\d{2,4}/g)
        .map(p => Number(p));
      const gc = moment(`${year}-${month}-${day}`, 'YYYY-M-D');
      const ec = gregorianToEthiopic(year, month, day);
      const moviesDetail = Object.create(null); // this will hold movie detail pages...
      const moviesTitle = Object.create(null); // this will hold movie titles...

      /**
       * first three div.row contain cinema 3, 2 & 1 respectively
       */
      const [c3, c2, c1] = $('body section#main div.row').splice(0, 3).map(cinema =>
        $('div', cinema).splice(1).map((div) => {
          const detail = `${config.EDNA_URL}${$('a', div).attr('href')}`;
          const title = $('a', div).text().trim();
          moviesDetail[detail] = detail;
          moviesTitle[title] = title;

          return {
            title,
            poster: $('img', div).attr('src'),
            time: $('ul', div).text().trim(),
            detail,
          };
        })
      );

      // going through details page and getting videos (if any)...
      // $('iframe[src*="youtube"]') | ('iframe[src*="imdb"]')
      const moviesDetailKeys = Object.keys(moviesDetail);
      const moviesDetailKeysPromises = moviesDetailKeys.map(detail => detailCache(moedoo, detail));

      Promise
        .all(moviesDetailKeysPromises)
        .then((moviesDetailKeysData) => {
          moviesDetailKeysData.forEach((moviesDetailKeyResponse, index) => {
            if (moviesDetailKeyResponse === null || typeof moviesDetailKeyResponse === 'string') {
              moviesDetail[moviesDetailKeys[index]] = moviesDetailKeyResponse;
            } else {
              const youtube = cheerio.load(moviesDetailKeyResponse.text)('iframe[src*="youtube"]');
              const imdb = cheerio.load(moviesDetailKeyResponse.text)('iframe[src*="imdb"]');
              moviesDetail[moviesDetailKeys[index]] = youtube.length > 0 ? youtube.attr('src') : null;
              // checking imdb embed if YouTube is not found...
              if (moviesDetail[moviesDetailKeys[index]] === null) {
                moviesDetail[moviesDetailKeys[index]] = imdb.length > 0 ? imdb.attr('src') : null;
              }

              detailCache(moedoo, moviesDetailKeys[index], moviesDetail[moviesDetailKeys[index]])
                .catch((err) => {
                  console.error(err);
                });
            }
          });

          // hitting omdb to get synopsis...
          const moviesTitleKeys = Object.keys(moviesTitle);
          const moviesTitleKeysPromises = moviesTitleKeys.map(title => omdbCache(moedoo, title));

          Promise
            .all(moviesTitleKeysPromises)
            .then((moviesTitleKeysData) => {
              moviesTitleKeysData.forEach((moviesTitleKeyResponse, index) => {
                if (moviesTitleKeyResponse === null || Object.prototype.hasOwnProperty.call(moviesTitleKeyResponse, 'body') === false) {
                  moviesTitle[moviesTitleKeys[index]] = moviesTitleKeyResponse;
                } else {
                  if (moviesTitleKeyResponse.body.Response === 'False') {
                    moviesTitle[moviesTitleKeys[index]] = null;
                  } else if (
                    moviesTitleKeyResponse.body.Title.toLocaleLowerCase() ===
                    moviesTitle[moviesTitleKeys[index]].toLocaleLowerCase()
                  ) {
                    moviesTitle[moviesTitleKeys[index]] = moviesTitleKeyResponse.body;
                  } else {
                    moviesTitle[moviesTitleKeys[index]] = null;
                  }

                  omdbCache(moedoo, moviesTitleKeys[index], moviesTitle[moviesTitleKeys[index]])
                    .catch((err) => {
                      console.error(err);
                    });
                }
              });

              // filling details...
              // we'll be mutating (fix your face, yes, mutating) initial c3, c2 & c1...
              [c3, c2, c1].forEach((c) => {
                c.forEach((show, index) => {
                  // eslint-disable-next-line
                  c[index].video = moviesDetail[c[index].detail];
                  // eslint-disable-next-line
                  c[index].omdb = moviesTitle[c[index].title];
                });
              });

              resolve({
                show: { c3, c2, c1 },
                meta: {
                  today: `${year}-${month}-${day}`, // YYYY-M-D
                  gc: `${gc.format('dddd, MMMM D')}`, // dddd, MMMM D
                  ec: `${gregorianWeekdayToEthiopicWeekday(gc.format('dddd'))}, ${ethiopicMonthToFullEthiopicMonth(ec.month)} ${ec.day}`, // dddd, MMMM D
                },
              });
            });
        });
    })
    .catch((err) => {
      reject(err);
    });
});
<|endoftext|>


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

CLASS({
  name: 'DragAndDropGrid',
  package: 'foam.apps.quickbug.ui',
  extendsModel: 'GridByExpr',

  requires: [
    'foam.apps.quickbug.ui.IssueDropCell'
  ],

  properties: [
    {
      name: 'dao'
    },
    {
      name: 'versionParser',
      factory: function() {
        return {
          __proto__: grammar,

          START: repeat(sym('component'), optional(sym('separator'))),

          component: alt(
            sym('number'),
            sym('string')
          ),

          digit: range('0', '9'),

          number: plus(sym('digit')),

          string: str(plus(not(alt(sym('digit'), sym('separator')), anyChar))),

          separator: alt('.', '-')
        }.addActions({
          number: function(v) { return parseInt(v.join('')); }
        });
      }
    }
  ],

  methods: {
    createVersionComparator_: function() {
      var m = {};

      function toKey(o) {
        return m[o] || ( m[o] = this.versionParser.parseString(o) );
      }

      return function(o1, o2) {
        o1 = toKey(o1);
        o2 = toKey(o2);

        for ( var i = 0 ; ; i++ ) {
          if ( i == o1.length && i == o2.length ) return 0;
          if ( i == o1.length ) return -1;
          if ( i == o2.length ) return  1;
          if ( typeof o1[i] === 'string' && typeof o2[i] !== 'string' ) return  1;
          if ( typeof o1[i] !== 'string' && typeof o2[i] === 'string' ) return -1;
          var c = o2.compareTo(o1);
          if ( c !== 0 ) return c;
        }
      };
    },

    // Milestone and Iteration use the following specialized compator rule:
    //   1. Undefined values come first
    //   2. Next, number values are sorted in descending order
    //   3. Next, string values are sorted in ascending order
    // TODO: this can be moved to the Model now.
    sortAxis: function(values, f) {
      return values.sort(
        f.name === 'milestone' || f.name === 'iteration' ?
          this.createVersionComparator_() :
          f.compareProperty);
    },
    renderCell: function(x, y, data) {
      var cell = this.IssueDropCell.create({
        data: data,
        dao: this.dao,
        props: [this.xFunc, this.yFunc],
        values: [x, y]
      }, this.Y);
      this.children.push(cell);
      return cell.toHTML();
    },
    clone: function() {
      var clone = this.SUPER();
      clone.dao = this.dao;
      return clone;
    }
  }
});
<commit_msg>Fix NPE in DragAndDropGrid due to referencing the wrong version parser.<commit_after>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

CLASS({
  name: 'DragAndDropGrid',
  package: 'foam.apps.quickbug.ui',
  extendsModel: 'GridByExpr',

  requires: [
    'foam.apps.quickbug.ui.IssueDropCell'
  ],

  properties: [
    {
      name: 'dao'
    },
    {
      name: 'versionParser',
      factory: function() {
        return {
          __proto__: grammar,

          START: repeat(sym('component'), optional(sym('separator'))),

          component: alt(
            sym('number'),
            sym('string')
          ),

          digit: range('0', '9'),

          number: plus(sym('digit')),

          string: str(plus(not(alt(sym('digit'), sym('separator')), anyChar))),

          separator: alt('.', '-')
        }.addActions({
          number: function(v) { return parseInt(v.join('')); }
        });
      }
    }
  ],

  methods: {
    createVersionComparator_: function() {
      var m = {};
      var parser = this.versionParser;

      function toKey(o) {
        return m[o] || ( m[o] = parser.parseString(o) );
      }

      return function(o1, o2) {
        o1 = toKey(o1);
        o2 = toKey(o2);

        for ( var i = 0 ; ; i++ ) {
          if ( i == o1.length && i == o2.length ) return 0;
          if ( i == o1.length ) return -1;
          if ( i == o2.length ) return  1;
          if ( typeof o1[i] === 'string' && typeof o2[i] !== 'string' ) return  1;
          if ( typeof o1[i] !== 'string' && typeof o2[i] === 'string' ) return -1;
          var c = o2.compareTo(o1);
          if ( c !== 0 ) return c;
        }
      };
    },

    // Milestone and Iteration use the following specialized compator rule:
    //   1. Undefined values come first
    //   2. Next, number values are sorted in descending order
    //   3. Next, string values are sorted in ascending order
    // TODO: this can be moved to the Model now.
    sortAxis: function(values, f) {
      return values.sort(
        f.name === 'milestone' || f.name === 'iteration' ?
          this.createVersionComparator_() :
          f.compareProperty);
    },
    renderCell: function(x, y, data) {
      var cell = this.IssueDropCell.create({
        data: data,
        dao: this.dao,
        props: [this.xFunc, this.yFunc],
        values: [x, y]
      }, this.Y);
      this.children.push(cell);
      return cell.toHTML();
    },
    clone: function() {
      var clone = this.SUPER();
      clone.dao = this.dao;
      return clone;
    }
  }
});
<|endoftext|>


<commit_before>const mongoose = require('mongoose');
mongoose.Promise = global.Promise;
const slug = require('slug');

const productSchema = new mongoose.Schema({
    name: {
        type: String,
        trim: true,
        required: 'Please enter a product name'
    }
});

module.exports = mongoose.model('Product', productSchema);<commit_msg>Initial model creation: Products<commit_after>const mongoose = require('mongoose');
mongoose.Promise = global.Promise;
const slug = require('slugs');

const productSchema = new mongoose.Schema({
    name: {
        type: String,
        trim: true,
        required: 'Please enter a product name'
    },
    description: {
        type: STRING,
        trim: true,
        required: 'Please enter a description'
    },
    price: {
        type: NUMBER,
        required: 'Please enter a Price'
    },
    thc: {
        type: NUMBER,
        required: 'Please enter a percentage in for the THC content'
    },
    cbd: {
        type: NUMBER,
        required: 'Please enter a percentage in for the CBD content'
    },
    slug: { type: STRING },
    photo: [ STRING ],
    tags: [ STRING ],
    created: {
        type: Date,
        default: Date.now
    }
});

storeSchema.pre('save', async function(next){
    if(!this.isModified('name')){
        next();
        return;
    }
    this.slug = slug(this.name);
    const slugRegEx = new RegExp(`^(${this.slug})((-[0-9]*)?)$`, 'i');
    const storesWithSlug = await this.constructor.find({slug: slugRegEx});
    if(storesWithSlug.length){
        this.slug = `${this.slug}-${storesWithSlug.length+1}`
    }
    next();
});

module.exports = mongoose.model('Product', productSchema);<|endoftext|>


<commit_before>xn.test.suite("CSS Class Applier module tests", function(s) {
    s.tearDown = function() {
        document.getElementById("test").innerHTML = "";
    };

    s.test("isAppliedToRange tests", function(t) {
        var applier = rangy.createCssClassApplier("test");

        var testEl = document.getElementById("test");
        testEl.innerHTML = 'Test One x Two Three two test';
        var oneEl = document.getElementById("one"), twoEl = document.getElementById("two"), threeEl = document.getElementById("three");
        var range = rangy.createRangyRange();

        range.selectNode(oneEl);
        t.assert(applier.isAppliedToRange(range));

        range.selectNodeContents(oneEl);
        t.assert(applier.isAppliedToRange(range));

        range.selectNode(twoEl);
        t.assert(applier.isAppliedToRange(range));

        range.selectNode(threeEl);
        t.assert(applier.isAppliedToRange(range));

        range.selectNode(testEl);
        t.assertFalse(applier.isAppliedToRange(range));

        range.selectNodeContents(testEl);
        t.assertFalse(applier.isAppliedToRange(range));

        range.setStart(testEl.firstChild, 4);
        range.setEndAfter(oneEl);
        t.assertFalse(applier.isAppliedToRange(range));

        range.setStart(testEl.firstChild, 5);
        t.assert(applier.isAppliedToRange(range));

        range.setEnd(oneEl.nextSibling, 0);
        t.assert(applier.isAppliedToRange(range));

        range.setEnd(oneEl.nextSibling, 1);
        t.assertFalse(applier.isAppliedToRange(range));
    });

    s.test("toggleRange simple test 1", function(t) {
        var applier = rangy.createCssClassApplier("test", true);
        var testEl = document.getElementById("test");
        testEl.innerHTML = 'Test One test';
        var oneEl = document.getElementById("one");
        var range = rangy.createRangyRange();
        range.selectNodeContents(oneEl);
        applier.toggleRange(range);

        t.assertEquals(testEl.childNodes.length, 3);
        t.assertEquals(testEl.firstChild.data, "Test ");
        t.assertEquals(testEl.lastChild.data, " test");
        t.assertEquals(testEl.childNodes[1].tagName, "SPAN");
        t.assertEquals(testEl.childNodes[1].id, "one");
        t.assertEquals(testEl.childNodes[1].className, "");
        t.assertEquals(testEl.childNodes[1].childNodes.length, 1);
        t.assertEquals(testEl.childNodes[1].firstChild.data, "One");

        applier.toggleRange(range);
        t.assertEquals(testEl.childNodes.length, 3);
        t.assertEquals(testEl.firstChild.data, "Test ");
        t.assertEquals(testEl.lastChild.data, " test");
        t.assertEquals(testEl.childNodes[1].tagName, "SPAN");
        t.assertEquals(testEl.childNodes[1].id, "one");
        t.assertEquals(testEl.childNodes[1].className, "test");
        t.assertEquals(testEl.childNodes[1].childNodes.length, 1);
        t.assertEquals(testEl.childNodes[1].firstChild.data, "One");
    });

    s.test("toggleRange simple test 2", function(t) {
        var applier = rangy.createCssClassApplier("test", true);
        var testEl = document.getElementById("test");
        testEl.innerHTML = 'Test One test';
        var oneEl = document.getElementById("one");
        var range = rangy.createRangyRange();
        range.selectNodeContents(oneEl);
        applier.toggleRange(range);

        t.assertEquals(testEl.childNodes.length, 3);
        t.assertEquals(testEl.firstChild.data, "Test ");
        t.assertEquals(testEl.lastChild.data, " test");
        t.assertEquals(testEl.childNodes[1].tagName, "SPAN");
        t.assertEquals(testEl.childNodes[1].id, "one");
        t.assertEquals(testEl.childNodes[1].className, "other");
        t.assertEquals(testEl.childNodes[1].childNodes.length, 1);
        t.assertEquals(testEl.childNodes[1].firstChild.data, "One");

        applier.toggleRange(range);
        t.assertEquals(testEl.childNodes.length, 3);
        t.assertEquals(testEl.firstChild.data, "Test ");
        t.assertEquals(testEl.lastChild.data, " test");
        t.assertEquals(testEl.childNodes[1].tagName, "SPAN");
        t.assertEquals(testEl.childNodes[1].id, "one");
        t.assertEquals(testEl.childNodes[1].className, "other test");
        t.assertEquals(testEl.childNodes[1].childNodes.length, 1);
        t.assertEquals(testEl.childNodes[1].firstChild.data, "One");
    });

    s.test("toggleRange nested in other class test", function(t) {
        var applier = rangy.createCssClassApplier("test", true);
        var testEl = document.getElementById("test");
        testEl.innerHTML = 'Before One after';
        var oneEl = document.getElementById("one");
        var range = rangy.createRangyRange();
        range.setStart(oneEl.firstChild, 1);
        range.setEnd(oneEl.firstChild, 2);
        applier.toggleRange(range);

        t.assertEquals(oneEl.childNodes.length, 3);
        t.assertEquals(oneEl.className, "other");
        t.assertEquals(oneEl.firstChild.data, "O");
        t.assertEquals(oneEl.lastChild.data, "e");
        t.assertEquals(oneEl.childNodes[1].tagName, "SPAN");
        t.assertEquals(oneEl.childNodes[1].className, "test");
        t.assertEquals(oneEl.childNodes[1].childNodes.length, 1);
        t.assertEquals(oneEl.childNodes[1].firstChild.data, "n");

        //t.assertEquals(testEl.innerHTML, 'Before One after');
    });

    s.test("toggleRange range inside class test", function(t) {
        var applier = rangy.createCssClassApplier("test", true);
        var testEl = document.getElementById("test");
        testEl.innerHTML = 'Before One after';
        var oneEl = document.getElementById("one");
        var range = rangy.createRangyRange();
        range.setStart(oneEl.firstChild, 1);
        range.setEnd(oneEl.firstChild, 2);
        applier.toggleRange(range);

        t.assertEquals(oneEl.childNodes.length, 1);
        t.assertEquals(oneEl.className, "test");
        t.assertEquals(oneEl.firstChild.data, "O");
        //alert(testEl.innerHTML);
        t.assertEquals(oneEl.nextSibling.data, "n");
        t.assertEquals(oneEl.nextSibling.nextSibling.tagName, "SPAN");
        t.assertEquals(oneEl.nextSibling.nextSibling.className, "test");
        t.assertEquals(oneEl.nextSibling.nextSibling.childNodes.length, 1);
        t.assertEquals(oneEl.nextSibling.nextSibling.firstChild.data, "e");

        //t.assertEquals(testEl.innerHTML, 'Before One after');
    });

    function iterateNodes(node, func, includeSelf) {
        if (includeSelf) {
            func(node);
        }
        for (var i = 0, children = node.childNodes, len = children.length; i < len; ++i) {
            iterateNodes(children[i], func, true);
        }
    }

    function createRangeInHtml(containerEl, html) {
        containerEl.innerHTML = html;
        var range = rangy.createRange(), foundStart = false;
        iterateNodes(containerEl, function(node) {
            if (node.nodeType == 3) {
                var openBraceIndex = node.data.indexOf("{");
                if (openBraceIndex != -1) {
                    node.data = node.data.slice(0, openBraceIndex) + node.data.slice(openBraceIndex + 1);
                    range.setStart(node, openBraceIndex);
                    foundStart = true;
                }
                var closeBraceIndex = node.data.indexOf("}");
                if (closeBraceIndex != -1) {
                    node.data = node.data.slice(0, closeBraceIndex) + node.data.slice(closeBraceIndex + 1);
                    range.setEnd(node, closeBraceIndex);
                }
                var pipeIndex = node.data.indexOf("|");
                if (pipeIndex == 0) {
                    node.data = node.data.slice(1);
                    range[foundStart ? "setEnd" : "setStart"](node.parentNode, rangy.dom.getNodeIndex(node));
                    foundStart = true;
                } else if (pipeIndex == node.length) {
                    node.data = node.data.slice(0, -1);
                    range[foundStart ? "setEnd" : "setStart"](node.parentNode, rangy.dom.getNodeIndex(node) + 1);
                    foundStart = true;
                }

                pipeIndex = node.data.indexOf("|");
                if (pipeIndex == 0) {
                    node.data = node.data.slice(1);
                    range.setEnd(node.parentNode, rangy.dom.getNodeIndex(node));
                } else if (pipeIndex == node.length) {
                    node.data = node.data.slice(0, -1);
                    range.setEnd(node.parentNode, rangy.dom.getNodeIndex(node) + 1);
                }

                // Clear empty text node
                if (node.data.length == 0) {
                    node.parentNode.removeChild(node);
                }
            }
        }, false);
        return range;
    }

    function getSortedClassName(el) {
        return el.className.split(/\s+/).sort().join(" ");
    }

    function htmlAndRangeToString(containerEl, range) {
        function insertRangeBoundary(node, offset, isStart) {
            if (node.nodeType == 3) {
                var str = isStart ? "{" : "}";
                node.data = node.data.slice(0, offset) + str + node.data.slice(offset);
            } else if (node.nodeType == 1) {
                var textNode = document.createTextNode("|");
                if (offset == node.childNodes.length) {
                    node.appendChild(textNode);
                } else {
                    node.insertBefore(textNode, node.childNodes[offset]);
                }
            }
        }

        function getHtml(node, includeSelf) {
            var html = "";
            if (node.nodeType == 1) {
                html = "<" + node.tagName.toLowerCase();
                if (node.id) {
                    html += ' id="' + node.id + '"';
                }
                if (node.className) {
                    html += ' class="' + getSortedClassName(node) + '"';
                }
                html += ">";

                for (var i = 0, children = node.childNodes, len = children.length; i < len; ++i) {
                    html += getHtml(children[i], true);
                }
                html += "</" + node.tagName.toLowerCase() + ">";
            } else if (node.nodeType == 3) {
                html += node.data;
            }
            return html;
        }

        insertRangeBoundary(range.endContainer, range.endOffset, false);
        insertRangeBoundary(range.startContainer, range.startOffset, true);

        return getHtml(containerEl, false);
    }

    s.test("Blah", function(t) {
        var testEl = document.getElementById("test");
        var range = createRangeInHtml(testEl, 'Before {One} after');
        alert(range.inspect());
        alert(htmlAndRangeToString(testEl, range));
    });

}, false);
<commit_msg>Work on CssClassApplier tests<commit_after>xn.test.suite("CSS Class Applier module tests", function(s) {
    s.tearDown = function() {
        document.getElementById("test").innerHTML = "";
    };

    s.test("isAppliedToRange tests", function(t) {
        var applier = rangy.createCssClassApplier("test");

        var testEl = document.getElementById("test");
        testEl.innerHTML = 'Test One x Two Three two test';
        var oneEl = document.getElementById("one"), twoEl = document.getElementById("two"), threeEl = document.getElementById("three");
        var range = rangy.createRangyRange();

        range.selectNode(oneEl);
        t.assert(applier.isAppliedToRange(range));

        range.selectNodeContents(oneEl);
        t.assert(applier.isAppliedToRange(range));

        range.selectNode(twoEl);
        t.assert(applier.isAppliedToRange(range));

        range.selectNode(threeEl);
        t.assert(applier.isAppliedToRange(range));

        range.selectNode(testEl);
        t.assertFalse(applier.isAppliedToRange(range));

        range.selectNodeContents(testEl);
        t.assertFalse(applier.isAppliedToRange(range));

        range.setStart(testEl.firstChild, 4);
        range.setEndAfter(oneEl);
        t.assertFalse(applier.isAppliedToRange(range));

        range.setStart(testEl.firstChild, 5);
        t.assert(applier.isAppliedToRange(range));

        range.setEnd(oneEl.nextSibling, 0);
        t.assert(applier.isAppliedToRange(range));

        range.setEnd(oneEl.nextSibling, 1);
        t.assertFalse(applier.isAppliedToRange(range));
    });

    s.test("toggleRange simple test 1", function(t) {
        var applier = rangy.createCssClassApplier("test", true);
        var testEl = document.getElementById("test");
        testEl.innerHTML = 'Test One test';
        var oneEl = document.getElementById("one");
        var range = rangy.createRangyRange();
        range.selectNodeContents(oneEl);
        applier.toggleRange(range);

        t.assertEquals(testEl.childNodes.length, 3);
        t.assertEquals(testEl.firstChild.data, "Test ");
        t.assertEquals(testEl.lastChild.data, " test");
        t.assertEquals(testEl.childNodes[1].tagName, "SPAN");
        t.assertEquals(testEl.childNodes[1].id, "one");
        t.assertEquals(testEl.childNodes[1].className, "");
        t.assertEquals(testEl.childNodes[1].childNodes.length, 1);
        t.assertEquals(testEl.childNodes[1].firstChild.data, "One");

        applier.toggleRange(range);
        t.assertEquals(testEl.childNodes.length, 3);
        t.assertEquals(testEl.firstChild.data, "Test ");
        t.assertEquals(testEl.lastChild.data, " test");
        t.assertEquals(testEl.childNodes[1].tagName, "SPAN");
        t.assertEquals(testEl.childNodes[1].id, "one");
        t.assertEquals(testEl.childNodes[1].className, "test");
        t.assertEquals(testEl.childNodes[1].childNodes.length, 1);
        t.assertEquals(testEl.childNodes[1].firstChild.data, "One");
    });

    s.test("toggleRange simple test 2", function(t) {
        var applier = rangy.createCssClassApplier("test", true);
        var testEl = document.getElementById("test");
        testEl.innerHTML = 'Test One test';
        var oneEl = document.getElementById("one");
        var range = rangy.createRangyRange();
        range.selectNodeContents(oneEl);
        applier.toggleRange(range);

        t.assertEquals(testEl.childNodes.length, 3);
        t.assertEquals(testEl.firstChild.data, "Test ");
        t.assertEquals(testEl.lastChild.data, " test");
        t.assertEquals(testEl.childNodes[1].tagName, "SPAN");
        t.assertEquals(testEl.childNodes[1].id, "one");
        t.assertEquals(testEl.childNodes[1].className, "other");
        t.assertEquals(testEl.childNodes[1].childNodes.length, 1);
        t.assertEquals(testEl.childNodes[1].firstChild.data, "One");

        applier.toggleRange(range);
        t.assertEquals(testEl.childNodes.length, 3);
        t.assertEquals(testEl.firstChild.data, "Test ");
        t.assertEquals(testEl.lastChild.data, " test");
        t.assertEquals(testEl.childNodes[1].tagName, "SPAN");
        t.assertEquals(testEl.childNodes[1].id, "one");
        t.assertEquals(testEl.childNodes[1].className, "other test");
        t.assertEquals(testEl.childNodes[1].childNodes.length, 1);
        t.assertEquals(testEl.childNodes[1].firstChild.data, "One");
    });

    s.test("toggleRange nested in other class test", function(t) {
        var applier = rangy.createCssClassApplier("test", true);
        var testEl = document.getElementById("test");
        testEl.innerHTML = 'Before One after';
        var oneEl = document.getElementById("one");
        var range = rangy.createRangyRange();
        range.setStart(oneEl.firstChild, 1);
        range.setEnd(oneEl.firstChild, 2);
        applier.toggleRange(range);

        t.assertEquals(oneEl.childNodes.length, 3);
        t.assertEquals(oneEl.className, "other");
        t.assertEquals(oneEl.firstChild.data, "O");
        t.assertEquals(oneEl.lastChild.data, "e");
        t.assertEquals(oneEl.childNodes[1].tagName, "SPAN");
        t.assertEquals(oneEl.childNodes[1].className, "test");
        t.assertEquals(oneEl.childNodes[1].childNodes.length, 1);
        t.assertEquals(oneEl.childNodes[1].firstChild.data, "n");

        //t.assertEquals(testEl.innerHTML, 'Before One after');
    });

    s.test("toggleRange range inside class test", function(t) {
        var applier = rangy.createCssClassApplier("test", true);
        var testEl = document.getElementById("test");
        testEl.innerHTML = 'Before One after';
        var oneEl = document.getElementById("one");
        var range = rangy.createRangyRange();
        range.setStart(oneEl.firstChild, 1);
        range.setEnd(oneEl.firstChild, 2);
        applier.toggleRange(range);

        t.assertEquals(oneEl.childNodes.length, 1);
        t.assertEquals(oneEl.className, "test");
        t.assertEquals(oneEl.firstChild.data, "O");
        //alert(testEl.innerHTML);
        t.assertEquals(oneEl.nextSibling.data, "n");
        t.assertEquals(oneEl.nextSibling.nextSibling.tagName, "SPAN");
        t.assertEquals(oneEl.nextSibling.nextSibling.className, "test");
        t.assertEquals(oneEl.nextSibling.nextSibling.childNodes.length, 1);
        t.assertEquals(oneEl.nextSibling.nextSibling.firstChild.data, "e");

        //t.assertEquals(testEl.innerHTML, 'Before One after');
    });

    function iterateNodes(node, func, includeSelf) {
        if (includeSelf) {
            func(node);
        }
        for (var i = 0, children = node.childNodes, len = children.length; i < len; ++i) {
            iterateNodes(children[i], func, true);
        }
    }

    function RangeInfo() {

    }

    RangeInfo.prototype = {
        setStart: function(node, offset) {
            this.sc = node;
            this.so = offset;
        },
        setEnd: function(node, offset) {
            this.ec = node;
            this.eo = offset;
        }
    };

    function createRangeInHtml(containerEl, html) {
        containerEl.innerHTML = html;
        var range = rangy.createRange(), foundStart = false;
        var rangeInfo = new RangeInfo();
        iterateNodes(containerEl, function(node) {
            if (node.nodeType == 3) {
                var openBraceIndex = node.data.indexOf("{");
                if (openBraceIndex != -1) {
                    node.data = node.data.slice(0, openBraceIndex) + node.data.slice(openBraceIndex + 1);
                    log.debug("openBraceIndex: " + openBraceIndex + ", data: " + node.data);
                    rangeInfo.setStart(node, openBraceIndex);
                    foundStart = true;
                }

                var pipeIndex = node.data.indexOf("|");
                if (pipeIndex == 0) {
                    node.data = node.data.slice(1);
                    rangeInfo[foundStart ? "setEnd" : "setStart"](node.parentNode, rangy.dom.getNodeIndex(node));
                    foundStart = true;
                } else if (pipeIndex == node.length - 1) {
                    node.data = node.data.slice(0, -1);
                    rangeInfo[foundStart ? "setEnd" : "setStart"](node.parentNode, rangy.dom.getNodeIndex(node) + 1);
                    foundStart = true;
                }

                var closeBraceIndex = node.data.indexOf("}");
                if (closeBraceIndex != -1) {
                    node.data = node.data.slice(0, closeBraceIndex) + node.data.slice(closeBraceIndex + 1);
                    log.debug("openBraceIndex: " + openBraceIndex + ", data: " + node.data);
                    rangeInfo.setEnd(node, closeBraceIndex);
                }

                pipeIndex = node.data.indexOf("|");
                if (pipeIndex == 0) {
                    node.data = node.data.slice(1);
                    rangeInfo.setEnd(node.parentNode, rangy.dom.getNodeIndex(node));
                } else if (pipeIndex == node.length - 1) {
                    node.data = node.data.slice(0, -1);
                    rangeInfo.setEnd(node.parentNode, rangy.dom.getNodeIndex(node) + 1);
                }

                // Clear empty text node
                if (node.data.length == 0) {
                    node.parentNode.removeChild(node);
                }
            }
        }, false);

        range.setStart(rangeInfo.sc, rangeInfo.so);
        range.setEnd(rangeInfo.ec, rangeInfo.eo);

        return range;
    }

    function getSortedClassName(el) {
        return el.className.split(/\s+/).sort().join(" ");
    }

    function htmlAndRangeToString(containerEl, range) {
        function insertRangeBoundary(node, offset, isStart) {
            if (node.nodeType == 3) {
                var str = isStart ? "{" : "}";
                node.data = node.data.slice(0, offset) + str + node.data.slice(offset);
            } else if (node.nodeType == 1) {
                var textNode = document.createTextNode("|");
                if (offset == node.childNodes.length) {
                    node.appendChild(textNode);
                } else {
                    node.insertBefore(textNode, node.childNodes[offset]);
                }
            }
        }

        function getHtml(node, includeSelf) {
            var html = "";
            if (node.nodeType == 1) {
                if (includeSelf) {
                    html = "<" + node.tagName.toLowerCase();
                    if (node.id) {
                        html += ' id="' + node.id + '"';
                    }
                    if (node.className) {
                        html += ' class="' + getSortedClassName(node) + '"';
                    }
                    html += ">";
                }

                for (var i = 0, children = node.childNodes, len = children.length; i < len; ++i) {
                    html += getHtml(children[i], true);
                }

                if (includeSelf) {
                    html += "</" + node.tagName.toLowerCase() + ">";
                }
            } else if (includeSelf && node.nodeType == 3) {
                html += node.data;
            }
            return html;
        }

        insertRangeBoundary(range.endContainer, range.endOffset, false);
        insertRangeBoundary(range.startContainer, range.startOffset, true);

        return getHtml(containerEl, false);
    }

    function testRangeHtml(testEl, html, t) {
        var range = createRangeInHtml(testEl, html);
        log.info("Range: " + range.inspect());
        var newHtml = htmlAndRangeToString(testEl, range);
        t.assertEquals(html, newHtml);
    }

    s.test("Test the Range/HTML test functions", function(t) {
        var testEl = document.getElementById("test");
        //var range = createRangeInHtml(testEl, 'Before {One} after');
        //alert(range.inspect());
        //alert(htmlAndRangeToString(testEl, range));
        testRangeHtml(testEl, 'Before {One} after', t);
        testRangeHtml(testEl, 'Before |On}e after', t);
        testRangeHtml(testEl, 'Before |One| after', t);
        testRangeHtml(testEl, 'Bef{ore One af}ter', t);
        testRangeHtml(testEl, 'Bef{ore |One after', t);
        testRangeHtml(testEl, '1{2}3', t);
    });

    /*
    See http://jsfiddle.net/QTs5U/
    and http://aryeh.name/spec/editcommands/autoimplementation.html
     */

    s.test("Test multiple classes", function(t) {
        var applier1 = rangy.createCssClassApplier("c1"),
            applier2 = rangy.createCssClassApplier("c2");

        var testEl = document.getElementById("test");
        var range = createRangeInHtml(testEl, "1{234}5");

        applier1.applyToRange(range);
        t.assertEquals('1{234}5', htmlAndRangeToString(testEl, range));

        range.setStart(range.startContainer, range.startOffset + 1);
        applier1.applyToRange(range);
        t.assertEquals('1{234}5', htmlAndRangeToString(testEl, range));

        applier1.applyToRange(range);
        t.assertEquals('1{234}5', htmlAndRangeToString(testEl, range));

    });

}, false);
<|endoftext|>