Fixed comment ownership. (#2923)

* Moved comment icons to use a model-based system. The block holds the model of the comment, and the comment icon holds a reference to it.
* Reorganized the setVisible function.
* Changed how xml.js serializes and deserializes comments.
This commit is contained in:
Beka Westberg
2019-09-20 13:16:07 -07:00
committed by Sam El-Husseini
parent 0726e4a909
commit 9e5df6216a
17 changed files with 890 additions and 219 deletions

View File

@@ -40,7 +40,6 @@ goog.require('Blockly.utils.Coordinate');
goog.require('Blockly.utils.object');
goog.require('Blockly.fieldRegistry');
goog.require('Blockly.utils.string');
goog.require('Blockly.Warning');
goog.require('Blockly.Workspace');
@@ -128,9 +127,24 @@ Blockly.Block = function(workspace, prototypeName, opt_id) {
*/
this.collapsed_ = false;
/** @type {string|Blockly.Comment} */
/**
* A string representing the comment attached to this block.
* @type {string|Blockly.Comment}
* @deprecated August 2019. Use getCommentText instead.
*/
this.comment = null;
/**
* A model of the comment attached to this block.
* @type {!Blockly.Block.CommentModel}
* @package
*/
this.commentModel = {
text: null,
pinned: false,
size: new Blockly.utils.Size(160, 80)
};
/**
* The block's position in workspace units. (0, 0) is at the workspace's
* origin; scale does not change this value.
@@ -205,6 +219,15 @@ Blockly.Block = function(workspace, prototypeName, opt_id) {
}
};
/**
* @typedef {{
* text:?string,
* pinned:boolean,
* size:Blockly.utils.Size
* }}
*/
Blockly.Block.CommentModel;
/**
* Optional text data that round-trips between blocks and XML.
* Has no effect. May be used by 3rd parties for meta information.
@@ -1783,11 +1806,11 @@ Blockly.Block.prototype.getInputTargetBlock = function(name) {
};
/**
* Returns the comment on this block (or '' if none).
* Returns the comment on this block (or null if there is no comment).
* @return {string} Block's comment.
*/
Blockly.Block.prototype.getCommentText = function() {
return this.comment || '';
return this.commentModel.text;
};
/**
@@ -1795,11 +1818,13 @@ Blockly.Block.prototype.getCommentText = function() {
* @param {?string} text The text, or null to delete.
*/
Blockly.Block.prototype.setCommentText = function(text) {
if (this.comment != text) {
Blockly.Events.fire(new Blockly.Events.BlockChange(
this, 'comment', null, this.comment, text || ''));
this.comment = text;
if (this.commentModel.text == text) {
return;
}
Blockly.Events.fire(new Blockly.Events.BlockChange(
this, 'comment', null, this.commentModel.text, text));
this.commentModel.text = text;
this.comment = text; // For backwards compatibility.
};
/**

View File

@@ -42,6 +42,7 @@ goog.require('Blockly.utils.Coordinate');
goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.object');
goog.require('Blockly.utils.Rect');
goog.require('Blockly.Warning');
/**
@@ -258,9 +259,17 @@ Blockly.BlockSvg.prototype.mutator = null;
/**
* Block's comment icon (if any).
* @type {Blockly.Comment}
* @deprecated August 2019. Use getCommentIcon instead.
*/
Blockly.BlockSvg.prototype.comment = null;
/**
* Block's comment icon (if any).
* @type {Blockly.Comment}
* @private
*/
Blockly.BlockSvg.prototype.commentIcon_ = null;
/**
* Block's warning icon (if any).
* @type {Blockly.Warning}
@@ -276,8 +285,8 @@ Blockly.BlockSvg.prototype.getIcons = function() {
if (this.mutator) {
icons.push(this.mutator);
}
if (this.comment) {
icons.push(this.comment);
if (this.commentIcon_) {
icons.push(this.commentIcon_);
}
if (this.warning) {
icons.push(this.warning);
@@ -951,28 +960,11 @@ Blockly.BlockSvg.prototype.dispose = function(healStack, animate) {
this.warningTextDb_ = null;
}
// If the block is rendered we need to record the event before disposing of
// the icons to prevent losing information.
// TODO (#1969): Remove event generation/firing once comments are fixed.
this.unplug(healStack);
var deleteEvent;
if (Blockly.Events.isEnabled()) {
deleteEvent = new Blockly.Events.BlockDelete(this);
}
Blockly.Events.disable();
try {
var icons = this.getIcons();
for (var i = 0; i < icons.length; i++) {
icons[i].dispose();
}
// TODO (#1969): Move out of disable block once comments are fixed.
Blockly.BlockSvg.superClass_.dispose.call(this, healStack);
} finally {
Blockly.Events.enable();
}
if (Blockly.Events.isEnabled() && deleteEvent) {
Blockly.Events.fire(deleteEvent);
var icons = this.getIcons();
for (var i = 0; i < icons.length; i++) {
icons[i].dispose();
}
Blockly.BlockSvg.superClass_.dispose.call(this, healStack);
Blockly.utils.dom.removeNode(this.svgGroup_);
blockWorkspace.resizeContents();
@@ -1072,16 +1064,12 @@ Blockly.BlockSvg.prototype.updateDisabled = function() {
};
/**
* Returns the comment on this block (or '' if none).
* @return {string} Block's comment.
* Get the comment icon attached to this block, or null if the block has no
* comment.
* @return {Blockly.Comment} The comment icon attached to this block, or null.
*/
Blockly.BlockSvg.prototype.getCommentText = function() {
if (this.comment) {
var comment = this.comment.getText();
// Trim off trailing whitespace.
return comment.replace(/\s+$/, '').replace(/ +\n/g, '\n');
}
return '';
Blockly.BlockSvg.prototype.getCommentIcon = function() {
return this.commentIcon_;
};
/**
@@ -1089,23 +1077,30 @@ Blockly.BlockSvg.prototype.getCommentText = function() {
* @param {?string} text The text, or null to delete.
*/
Blockly.BlockSvg.prototype.setCommentText = function(text) {
var changedState = false;
if (typeof text == 'string') {
if (!this.comment) {
if (!Blockly.Comment) {
throw Error('Missing require for Blockly.Comment');
}
this.comment = new Blockly.Comment(this);
changedState = true;
}
this.comment.setText(/** @type {string} */ (text));
} else {
if (this.comment) {
this.comment.dispose();
changedState = true;
}
if (!Blockly.Comment) {
throw Error('Missing require for Blockly.Comment');
}
if (changedState && this.rendered) {
if (this.commentModel.text == text) {
return;
}
Blockly.BlockSvg.superClass_.setCommentText.call(this, text);
var shouldHaveComment = text != null;
if (!!this.commentIcon_ == shouldHaveComment) {
// If the comment's state of existence is correct, but the text is new
// that means we're just updating a comment.
this.commentIcon_.updateText();
return;
}
if (shouldHaveComment) {
this.commentIcon_ = new Blockly.Comment(this);
this.comment = this.commentIcon_; // For backwards compatibility.
} else {
this.commentIcon_.dispose();
this.commentIcon_ = null;
this.comment = null; // For backwards compatibility.
}
if (this.rendered) {
this.render();
// Adding or removing a comment icon will cause the block to change shape.
this.bumpNeighbours_();

View File

@@ -625,10 +625,10 @@ Blockly.Bubble.prototype.moveTo = function(x, y) {
/**
* Get the dimensions of this bubble.
* @return {!Object} Object with width and height properties.
* @return {!Blockly.utils.Size} The height and width of the bubble.
*/
Blockly.Bubble.prototype.getBubbleSize = function() {
return {width: this.width_, height: this.height_};
return new Blockly.utils.Size(this.width_, this.height_);
};
/**
@@ -657,13 +657,12 @@ Blockly.Bubble.prototype.setBubbleSize = function(width, height) {
(height - doubleBorderWidth) + ')');
}
}
if (this.rendered_) {
if (this.autoLayout_) {
this.layoutBubble_();
}
this.positionBubble_();
this.renderArrow_();
if (this.autoLayout_) {
this.layoutBubble_();
}
this.positionBubble_();
this.renderArrow_();
// Allow the contents to resize.
if (this.resizeCallback_) {
this.resizeCallback_();

View File

@@ -44,28 +44,29 @@ goog.require('Blockly.utils.userAgent');
*/
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_ = '';
this.createIcon();
};
Blockly.utils.object.inherits(Blockly.Comment, Blockly.Icon);
/**
* Comment text (if bubble is not visible).
* @private
*/
Blockly.Comment.prototype.text_ = '';
/**
* Width of bubble.
* @private
*/
Blockly.Comment.prototype.width_ = 160;
/**
* Height of bubble.
* @private
*/
Blockly.Comment.prototype.height_ = 80;
/**
* Draw the comment icon.
* @param {!Element} group The icon group.
@@ -104,7 +105,8 @@ Blockly.Comment.prototype.drawIcon_ = function(group) {
* @private
*/
Blockly.Comment.prototype.createEditor_ = function() {
/* Create the editor. Here's the markup that will be generated:
/* 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"
@@ -112,33 +114,49 @@ Blockly.Comment.prototype.createEditor_ = function() {
style="height: 164px; width: 164px;"></textarea>
</body>
</foreignObject>
*/
* For non-editable mode see Warning.textToDom_.
*/
this.foreignObject_ = Blockly.utils.dom.createSvgElement('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';
var textarea = document.createElementNS(Blockly.utils.dom.HTML_NS, 'textarea');
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.textarea_ = textarea;
this.foreignObject_.appendChild(body);
Blockly.bindEventWithChecks_(textarea, 'mouseup', this, this.textareaFocus_,
// 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.
Blockly.bindEventWithChecks_(textarea, 'mouseup', this, this.startEdit_,
true, true);
// Don't zoom with mousewheel.
Blockly.bindEventWithChecks_(textarea, 'wheel', this, function(e) {
e.stopPropagation();
});
Blockly.bindEventWithChecks_(textarea, 'change', this, function(_e) {
if (this.text_ != textarea.value) {
if (this.cachedText_ != this.model_.text) {
Blockly.Events.fire(new Blockly.Events.BlockChange(
this.block_, 'comment', null, this.text_, textarea.value));
this.text_ = textarea.value;
this.block_, 'comment', null, this.cachedText_, this.model_.text));
}
});
Blockly.bindEventWithChecks_(textarea, 'input', this, function(_e) {
this.model_.text = textarea.value;
});
setTimeout(textarea.focus.bind(textarea), 0);
return this.foreignObject_;
};
@@ -147,13 +165,12 @@ Blockly.Comment.prototype.createEditor_ = function() {
* @override
*/
Blockly.Comment.prototype.updateEditable = function() {
Blockly.Comment.superClass_.updateEditable.call(this);
if (this.isVisible()) {
// Toggling visibility will force a rerendering.
this.setVisible(false);
this.setVisible(true);
// Recreate the bubble with the correct UI.
this.disposeBubble_();
this.createBubble_();
}
// Allow the icon to update.
Blockly.Icon.prototype.updateEditable.call(this);
};
/**
@@ -161,15 +178,28 @@ Blockly.Comment.prototype.updateEditable = function() {
* Resize the text area accordingly.
* @private
*/
Blockly.Comment.prototype.resizeBubble_ = function() {
if (this.isVisible()) {
var size = this.bubble_.getBubbleSize();
var doubleBorderWidth = 2 * Blockly.Bubble.BORDER_WIDTH;
this.foreignObject_.setAttribute('width', size.width - doubleBorderWidth);
this.foreignObject_.setAttribute('height', size.height - doubleBorderWidth);
this.textarea_.style.width = (size.width - doubleBorderWidth - 4) + 'px';
this.textarea_.style.height = (size.height - doubleBorderWidth - 4) + 'px';
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';
};
/**
@@ -178,71 +208,99 @@ Blockly.Comment.prototype.resizeBubble_ = function() {
*/
Blockly.Comment.prototype.setVisible = function(visible) {
if (visible == this.isVisible()) {
// No change.
return;
}
Blockly.Events.fire(
new Blockly.Events.Ui(this.block_, 'commentOpen', !visible, visible));
if ((!this.block_.isEditable() && !this.textarea_) ||
Blockly.utils.userAgent.IE) {
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.
Blockly.Warning.prototype.setVisible.call(this, visible);
return;
}
// Save the bubble stats before the visibility switch.
var text = this.getText();
var size = this.getBubbleSize();
if (visible) {
// Create the bubble.
this.bubble_ = new Blockly.Bubble(
/** @type {!Blockly.WorkspaceSvg} */ (this.block_.workspace),
this.createEditor_(), this.block_.svgPath_,
this.iconXY_, this.width_, this.height_);
// Expose this comment's block's ID on its top-level SVG group.
this.bubble_.setSvgId(this.block_.id);
this.bubble_.registerResizeEvent(this.resizeBubble_.bind(this));
this.updateColour();
this.createNonEditableBubble_();
} else {
// Dispose of the bubble.
this.bubble_.dispose();
this.bubble_ = null;
this.textarea_ = null;
this.foreignObject_ = null;
this.createEditableBubble_();
}
// Restore the bubble stats after the visibility switch.
this.setText(text);
this.setBubbleSize(size.width, size.height);
};
/**
* Bring the comment to the top of the stack when clicked on.
* 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_.svgPath_,
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.updateColour();
};
/**
* Show a non-editable bubble.
* @private
*/
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
*/
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;
}
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.textareaFocus_ = function(_e) {
// 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.
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 {!Object} Object with width and height properties.
* @return {Blockly.utils.Size} Object with width and height properties.
*/
Blockly.Comment.prototype.getBubbleSize = function() {
if (this.isVisible()) {
return this.bubble_.getBubbleSize();
} else {
return {width: this.width_, height: this.height_};
}
return this.model_.size;
};
/**
@@ -251,44 +309,60 @@ Blockly.Comment.prototype.getBubbleSize = function() {
* @param {number} height Height of the bubble.
*/
Blockly.Comment.prototype.setBubbleSize = function(width, height) {
if (this.textarea_) {
if (this.bubble_) {
this.bubble_.setBubbleSize(width, height);
} else {
this.width_ = width;
this.height_ = height;
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() {
return this.textarea_ ? this.textarea_.value : this.text_;
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) {
if (this.text_ != text) {
Blockly.Events.fire(new Blockly.Events.BlockChange(
this.block_, 'comment', null, this.text_, text));
this.text_ = text;
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 = text;
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() {
if (Blockly.Events.isEnabled()) {
this.setText(''); // Fire event to delete comment.
}
this.block_.comment = null;
Blockly.Icon.prototype.dispose.call(this);
};

View File

@@ -34,10 +34,15 @@ goog.require('Blockly.utils.Size');
/**
* Class for an icon.
* @param {Blockly.Block} block The block associated with this icon.
* @param {Blockly.BlockSvg} block The block associated with this icon.
* @constructor
*/
Blockly.Icon = function(block) {
/**
* The block this icon is attached to.
* @type {Blockly.BlockSvg}
* @protected
*/
this.block_ = block;
};
@@ -108,6 +113,7 @@ Blockly.Icon.prototype.dispose = function() {
* Add or remove the UI indicating if this icon may be clicked or not.
*/
Blockly.Icon.prototype.updateEditable = function() {
// No-op on the base class.
};
/**

View File

@@ -183,6 +183,7 @@ Blockly.Mutator.prototype.createEditor_ = function() {
* Add or remove the UI indicating if this icon may be clicked or not.
*/
Blockly.Mutator.prototype.updateEditable = function() {
Blockly.Mutator.superClass_.updateEditable.call(this);
if (!this.block_.isInFlyout) {
if (this.block_.isEditable()) {
if (this.iconGroup_) {
@@ -200,8 +201,6 @@ Blockly.Mutator.prototype.updateEditable = function() {
}
}
}
// Default behaviour for an icon.
Blockly.Icon.prototype.updateEditable.call(this);
};
/**
@@ -297,7 +296,6 @@ Blockly.Mutator.prototype.setVisible = function(visible) {
};
this.block_.workspace.addChangeListener(this.sourceListener_);
}
this.resizeBubble_();
// When the mutator's workspace changes, update the source block.
this.workspace_.addChangeListener(this.workspaceChanged_.bind(this));
this.updateColour();

View File

@@ -116,40 +116,57 @@ Blockly.Warning.textToDom_ = function(text) {
*/
Blockly.Warning.prototype.setVisible = function(visible) {
if (visible == this.isVisible()) {
// No change.
return;
}
Blockly.Events.fire(
new Blockly.Events.Ui(this.block_, 'warningOpen', !visible, visible));
if (visible) {
// Create the bubble to display all warnings.
var paragraph = Blockly.Warning.textToDom_(this.getText());
this.bubble_ = new Blockly.Bubble(
/** @type {!Blockly.WorkspaceSvg} */ (this.block_.workspace),
paragraph, this.block_.svgPath_, this.iconXY_, null, null);
// Expose this warning's block's ID on its top-level SVG group.
this.bubble_.setSvgId(this.block_.id);
if (this.block_.RTL) {
// Right-align the paragraph.
// This cannot be done until the bubble is rendered on screen.
var maxWidth = paragraph.getBBox().width;
for (var i = 0, textElement; textElement = paragraph.childNodes[i]; i++) {
textElement.setAttribute('text-anchor', 'end');
textElement.setAttribute('x', maxWidth + Blockly.Bubble.BORDER_WIDTH);
}
}
this.updateColour();
// Bump the warning into the right location.
var size = this.bubble_.getBubbleSize();
this.bubble_.setBubbleSize(size.width, size.height);
this.createBubble();
} else {
// Dispose of the bubble.
this.bubble_.dispose();
this.bubble_ = null;
this.body_ = null;
this.disposeBubble();
}
};
/**
* Show the bubble.
* @package
*/
Blockly.Warning.prototype.createBubble = function() {
// TODO (#2943): This is package because comments steal this UI for
// non-editable comments, but really this should be private.
this.paragraphElement_ = Blockly.Warning.textToDom_(this.getText());
this.bubble_ = new Blockly.Bubble(
/** @type {!Blockly.WorkspaceSvg} */ (this.block_.workspace),
this.paragraphElement_, this.block_.svgPath_, this.iconXY_, null, null);
// Expose this warning's block's ID on its top-level SVG group.
this.bubble_.setSvgId(this.block_.id);
if (this.block_.RTL) {
// Right-align the paragraph.
// This cannot be done until the bubble is rendered on screen.
var maxWidth = this.paragraphElement_.getBBox().width;
for (var i = 0, textElement;
textElement = this.paragraphElement_.childNodes[i]; i++) {
textElement.setAttribute('text-anchor', 'end');
textElement.setAttribute('x', maxWidth + Blockly.Bubble.BORDER_WIDTH);
}
}
this.updateColour();
};
/**
* Dispose of the bubble and references to it.
* @package
*/
Blockly.Warning.prototype.disposeBubble = function() {
// TODO (#2943): This is package because comments steal this UI for
// non-editable comments, but really this should be private.
this.bubble_.dispose();
this.bubble_ = null;
this.body_ = null;
this.paragraphElement_ = null;
};
/**
* Bring the warning to the top of the stack when clicked on.
* @param {!Event} _e Mouse up event.
@@ -162,7 +179,8 @@ Blockly.Warning.prototype.bodyFocus_ = function(_e) {
/**
* Set this warning's text.
* @param {string} text Warning text (or '' to delete).
* @param {string} text Warning text (or '' to delete). This supports
* linebreaks.
* @param {string} id An ID for this text entry to be able to maintain
* multiple warnings.
*/

View File

@@ -163,14 +163,15 @@ Blockly.Xml.blockToDom = function(block, opt_noId) {
var commentText = block.getCommentText();
if (commentText) {
var size = block.commentModel.size;
var pinned = block.commentModel.pinned;
var commentElement = Blockly.utils.xml.createElement('comment');
commentElement.appendChild(Blockly.utils.xml.createTextNode(commentText));
if (typeof block.comment == 'object') {
commentElement.setAttribute('pinned', block.comment.isVisible());
var hw = block.comment.getBubbleSize();
commentElement.setAttribute('h', hw.height);
commentElement.setAttribute('w', hw.width);
}
commentElement.setAttribute('pinned', pinned);
commentElement.setAttribute('h', size.height);
commentElement.setAttribute('w', size.width);
element.appendChild(commentElement);
}
@@ -655,22 +656,21 @@ Blockly.Xml.domToBlockHeadless_ = function(xmlBlock, workspace) {
}
break;
case 'comment':
block.setCommentText(xmlChild.textContent);
var visible = xmlChild.getAttribute('pinned');
if (visible && !block.isInFlyout) {
// Give the renderer a millisecond to render and position the block
// before positioning the comment bubble.
setTimeout(function() {
if (block.comment && block.comment.setVisible) {
block.comment.setVisible(visible == 'true');
}
}, 1);
var text = xmlChild.textContent;
var pinned = xmlChild.getAttribute('pinned') == 'true';
var width = parseInt(xmlChild.getAttribute('w'), 10);
var height = parseInt(xmlChild.getAttribute('h'), 10);
block.setCommentText(text);
block.commentModel.pinned = pinned;
if (!isNaN(width) && !isNaN(height)) {
block.commentModel.size = new Blockly.utils.Size(width, height);
}
var bubbleW = parseInt(xmlChild.getAttribute('w'), 10);
var bubbleH = parseInt(xmlChild.getAttribute('h'), 10);
if (!isNaN(bubbleW) && !isNaN(bubbleH) &&
block.comment && block.comment.setVisible) {
block.comment.setBubbleSize(bubbleW, bubbleH);
if (pinned && block.getCommentIcon && !block.isInFlyout) {
setTimeout(function() {
block.getCommentIcon().setVisible(true);
}, 1);
}
break;
case 'data':

View File

@@ -213,8 +213,9 @@ Blockly.Dart.scrub_ = function(block, code, opt_thisOnly) {
if (!block.outputConnection || !block.outputConnection.targetConnection) {
// Collect comment for this block.
var comment = block.getCommentText();
comment = Blockly.utils.string.wrap(comment, Blockly.Dart.COMMENT_WRAP - 3);
if (comment) {
comment = Blockly.utils.string.wrap(comment,
Blockly.Dart.COMMENT_WRAP - 3);
if (block.getProcedureDef) {
// Use documentation comment for function comments.
commentCode += Blockly.Dart.prefixLines(comment + '\n', '/// ');

View File

@@ -254,9 +254,9 @@ Blockly.JavaScript.scrub_ = function(block, code, opt_thisOnly) {
if (!block.outputConnection || !block.outputConnection.targetConnection) {
// Collect comment for this block.
var comment = block.getCommentText();
comment = Blockly.utils.string.wrap(comment,
Blockly.JavaScript.COMMENT_WRAP - 3);
if (comment) {
comment = Blockly.utils.string.wrap(comment,
Blockly.JavaScript.COMMENT_WRAP - 3);
if (block.getProcedureDef) {
// Use a comment block for function comments.
commentCode += '/**\n' +

View File

@@ -187,8 +187,9 @@ Blockly.Lua.scrub_ = function(block, code, opt_thisOnly) {
if (!block.outputConnection || !block.outputConnection.targetConnection) {
// Collect comment for this block.
var comment = block.getCommentText();
comment = Blockly.utils.string.wrap(comment, Blockly.Lua.COMMENT_WRAP - 3);
if (comment) {
comment = Blockly.utils.string.wrap(comment,
Blockly.Lua.COMMENT_WRAP - 3);
commentCode += Blockly.Lua.prefixLines(comment, '-- ') + '\n';
}
// Collect comments for all value arguments.

View File

@@ -240,8 +240,9 @@ Blockly.PHP.scrub_ = function(block, code, opt_thisOnly) {
if (!block.outputConnection || !block.outputConnection.targetConnection) {
// Collect comment for this block.
var comment = block.getCommentText();
comment = Blockly.utils.string.wrap(comment, Blockly.PHP.COMMENT_WRAP - 3);
if (comment) {
comment = Blockly.utils.string.wrap(comment,
Blockly.PHP.COMMENT_WRAP - 3);
commentCode += Blockly.PHP.prefixLines(comment, '// ') + '\n';
}
// Collect comments for all value arguments.

View File

@@ -268,9 +268,9 @@ Blockly.Python.scrub_ = function(block, code, opt_thisOnly) {
if (!block.outputConnection || !block.outputConnection.targetConnection) {
// Collect comment for this block.
var comment = block.getCommentText();
comment = Blockly.utils.string.wrap(comment,
Blockly.Python.COMMENT_WRAP - 3);
if (comment) {
comment = Blockly.utils.string.wrap(comment,
Blockly.Python.COMMENT_WRAP - 3);
if (block.getProcedureDef) {
// Use a comment block for function comments.
commentCode += '"""' + comment + '\n"""\n';

View File

@@ -410,11 +410,137 @@ suite('Blocks', function() {
.connect(blockB.previousConnection);
this.blockA.removeInput('STATEMENT');
console.log(blockB.disposed, blockB);
chai.assert.isTrue(blockB.disposed);
chai.assert.equal(this.blockA.getChildren().length, 0);
});
});
});
});
suite('Comments', function() {
setup(function() {
Blockly.defineBlocksWithJsonArray([
{
"type": "empty_block",
"message0": "",
"args0": []
},
]);
this.eventSpy = sinon.spy(Blockly.Events, 'fire');
});
teardown(function() {
delete Blockly.Blocks['empty_block'];
this.eventSpy.restore();
});
suite('Set/Get Text', function() {
function assertCommentEvent(eventSpy, oldValue, newValue) {
var calls = eventSpy.getCalls();
var event = calls[calls.length - 1].args[0];
chai.assert.equal(event.type, Blockly.Events.BLOCK_CHANGE);
chai.assert.equal(event.element, 'comment');
chai.assert.equal(event.oldValue, oldValue);
chai.assert.equal(event.newValue, newValue);
}
function assertNoCommentEvent(eventSpy) {
var calls = eventSpy.getCalls();
var event = calls[calls.length - 1].args[0];
chai.assert.notEqual(event.type, Blockly.Events.BLOCK_CHANGE);
}
suite('Headless', function() {
setup(function() {
this.block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
'<block type="empty_block"/>'
), this.workspace);
});
test('Text', function() {
this.block.setCommentText('test text');
chai.assert.equal(this.block.getCommentText(), 'test text');
assertCommentEvent(this.eventSpy, null, 'test text');
});
test('Text Empty', function() {
this.block.setCommentText('');
chai.assert.equal(this.block.getCommentText(), '');
assertCommentEvent(this.eventSpy, null, '');
});
test('Text Null', function() {
this.block.setCommentText(null);
chai.assert.equal(this.block.getCommentText(), null);
assertNoCommentEvent(this.eventSpy);
});
test('Text -> Null', function() {
this.block.setCommentText('first text');
this.block.setCommentText(null);
chai.assert.equal(this.block.getCommentText(), null);
assertCommentEvent(this.eventSpy, 'first text', null);
});
});
suite('Rendered', function() {
setup(function() {
// Let the parent teardown take care of this.
this.workspace = Blockly.inject('blocklyDiv', {
comments: true,
scrollbars: true
});
this.block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
'<block type="empty_block"/>'
), this.workspace);
});
test('Text', function() {
this.block.setCommentText('test text');
chai.assert.equal(this.block.getCommentText(), 'test text');
assertCommentEvent(this.eventSpy, null, 'test text');
});
test('Text Empty', function() {
this.block.setCommentText('');
chai.assert.equal(this.block.getCommentText(), '');
assertCommentEvent(this.eventSpy, null, '');
});
test('Text Null', function() {
this.block.setCommentText(null);
chai.assert.equal(this.block.getCommentText(), null);
assertNoCommentEvent(this.eventSpy);
});
test('Text -> Null', function() {
this.block.setCommentText('first text');
this.block.setCommentText(null);
chai.assert.equal(this.block.getCommentText(), null);
assertCommentEvent(this.eventSpy, 'first text', null);
});
test('Set While Visible - Editable', function() {
this.block.setCommentText('test1');
var icon = this.block.getCommentIcon();
icon.setVisible(true);
this.block.setCommentText('test2');
chai.assert.equal(this.block.getCommentText(), 'test2');
assertCommentEvent(this.eventSpy, 'test1', 'test2');
chai.assert.equal(icon.textarea_.value, 'test2');
});
test('Set While Visible - NonEditable', function() {
this.block.setCommentText('test1');
var editableStub = sinon.stub(this.block, 'isEditable').returns(false);
var icon = this.block.getCommentIcon();
icon.setVisible(true);
this.block.setCommentText('test2');
chai.assert.equal(this.block.getCommentText(), 'test2');
assertCommentEvent(this.eventSpy, 'test1', 'test2');
chai.assert.equal(icon.paragraphElement_.firstChild.textContent,
'test2');
editableStub.restore();
});
test('Get Text While Editing', function() {
this.block.setCommentText('test1');
var icon = this.block.getCommentIcon();
icon.setVisible(true);
icon.textarea_.value = 'test2';
icon.textarea_.dispatchEvent(new Event('input'));
chai.assert.equal(this.block.getCommentText(), 'test2');
});
});
});
});
});

144
tests/mocha/comment_test.js Normal file
View File

@@ -0,0 +1,144 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2019 Google Inc.
* https://developers.google.com/blockly/
*
* 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.
*/
suite('Comments', function() {
setup(function() {
Blockly.defineBlocksWithJsonArray([
{
"type": "empty_block",
"message0": "",
"args0": []
},
]);
this.workspace = Blockly.inject('blocklyDiv', {
comments: true,
scrollbars: true
});
this.block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
'<block type="empty_block"/>'
), this.workspace);
this.comment = new Blockly.Comment(this.block);
this.comment.computeIconLocation();
});
teardown(function() {
delete Blockly.Blocks['empty_block'];
this.workspace.dispose();
});
suite('Visibility and Editability', function() {
setup(function() {
this.comment.setText('test text');
this.eventSpy = sinon.stub(Blockly.Events, 'fire');
});
teardown(function() {
this.eventSpy.restore();
});
function assertEvent(eventSpy, type, element, oldValue, newValue) {
var calls = eventSpy.getCalls();
var event = calls[calls.length - 1].args[0];
chai.assert.equal(event.type, type);
chai.assert.equal(event.element, element);
chai.assert.equal(event.oldValue, oldValue);
chai.assert.equal(event.newValue, newValue);
}
function assertEditable(comment) {
chai.assert.isNotOk(comment.paragraphElement_);
chai.assert.isOk(comment.textarea_);
chai.assert.equal(comment.textarea_.value, 'test text');
}
function assertNotEditable(comment) {
chai.assert.isNotOk(comment.textarea_);
chai.assert.isOk(comment.paragraphElement_);
chai.assert.equal(comment.paragraphElement_.firstChild.textContent,
'test text');
}
test('Editable', function() {
this.comment.setVisible(true);
chai.assert.isTrue(this.comment.isVisible());
assertEditable(this.comment);
assertEvent(this.eventSpy, Blockly.Events.UI, 'commentOpen', false, true);
});
test('Not Editable', function() {
var editableStub = sinon.stub(this.block, 'isEditable').returns(false);
this.comment.setVisible(true);
chai.assert.isTrue(this.comment.isVisible());
assertNotEditable(this.comment);
assertEvent(this.eventSpy, Blockly.Events.UI, 'commentOpen', false, true);
editableStub.restore();
});
test('Editable -> Not Editable', function() {
this.comment.setVisible(true);
var editableStub = sinon.stub(this.block, 'isEditable').returns(false);
this.comment.updateEditable();
chai.assert.isTrue(this.comment.isVisible());
assertNotEditable(this.comment);
assertEvent(this.eventSpy, Blockly.Events.UI, 'commentOpen', false, true);
editableStub.restore();
});
test('Not Editable -> Editable', function() {
var editableStub = sinon.stub(this.block, 'isEditable').returns(false);
this.comment.setVisible(true);
editableStub.returns(true);
this.comment.updateEditable();
chai.assert.isTrue(this.comment.isVisible());
assertEditable(this.comment);
assertEvent(this.eventSpy, Blockly.Events.UI, 'commentOpen', false, true);
editableStub.restore();
});
});
suite('Set/Get Bubble Size', function() {
function assertBubbleSize(comment, height, width) {
var size = comment.getBubbleSize();
chai.assert.equal(size.height, height);
chai.assert.equal(size.width, width);
}
function assertBubbleSizeDefault(comment) {
assertBubbleSize(comment, 80, 160);
}
test('Set Size While Visible', function() {
this.comment.setVisible(true);
var bubbleSizeSpy = sinon.spy(this.comment.bubble_, 'setBubbleSize');
assertBubbleSizeDefault(this.comment);
this.comment.setBubbleSize(100, 100);
assertBubbleSize(this.comment, 100, 100);
chai.assert(bubbleSizeSpy.calledOnce);
this.comment.setVisible(false);
assertBubbleSize(this.comment, 100, 100);
bubbleSizeSpy.restore();
});
test('Set Size While Invisible', function() {
assertBubbleSizeDefault(this.comment);
this.comment.setBubbleSize(100, 100);
assertBubbleSize(this.comment, 100, 100);
this.comment.setVisible(true);
assertBubbleSize(this.comment, 100, 100);
});
});
});

View File

@@ -26,6 +26,7 @@
<script src="astnode_test.js"></script>
<script src="block_test.js"></script>
<script src="comment_test.js"></script>
<script src="connection_db_test.js"></script>
<script src="connection_test.js"></script>
<script src="cursor_test.js"></script>

View File

@@ -19,12 +19,6 @@
*/
suite('XML', function() {
setup(function() {
this.workspace = new Blockly.Workspace();
});
teardown(function() {
this.workspace.dispose();
});
var assertSimpleField = function(fieldDom, name, text) {
assertEquals(text, fieldDom.textContent);
assertEquals(name, fieldDom.getAttribute('name'));
@@ -38,7 +32,25 @@ suite('XML', function() {
assertEquals(id, fieldDom.getAttribute('id'));
assertEquals(text, fieldDom.textContent);
};
setup(function() {
Blockly.defineBlocksWithJsonArray([
{
"type": "empty_block",
"message0": "",
"args0": []
},
]);
});
teardown(function() {
delete Blockly.Blocks['empty_block'];
});
suite('Serialization', function() {
setup(function() {
this.workspace = new Blockly.Workspace();
});
teardown(function() {
this.workspace.dispose();
});
suite('Fields', function() {
test('Angle', function() {
Blockly.defineBlocksWithJsonArray([{
@@ -201,7 +213,6 @@ suite('XML', function() {
var block = new Blockly.Block(this.workspace,
'field_label_serializable_test_block');
var resultFieldDom = Blockly.Xml.blockToDom(block).childNodes[0];
console.log(resultFieldDom);
assertSimpleField(resultFieldDom, 'LABEL', 'default');
delete Blockly.Blocks['field_label_serializable_test_block'];
});
@@ -289,8 +300,88 @@ suite('XML', function() {
});
});
});
suite('Comments', function() {
suite('Headless', function() {
setup(function() {
this.block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
'<block type="empty_block"/>'
), this.workspace);
});
test('Text', function() {
this.block.setCommentText('test text');
var xml = Blockly.Xml.blockToDom(this.block);
var commentXml = xml.firstChild;
chai.assert.equal(commentXml.tagName, 'comment');
chai.assert.equal(commentXml.innerHTML, 'test text');
});
test('No Text', function() {
var xml = Blockly.Xml.blockToDom(this.block);
chai.assert.isNull(xml.firstChild);
});
test('Empty Text', function() {
this.block.setCommentText('');
var xml = Blockly.Xml.blockToDom(this.block);
chai.assert.isNull(xml.firstChild);
});
});
suite('Rendered', function() {
setup(function() {
// Let the parent teardown dispose of it.
this.workspace = Blockly.inject('blocklyDiv', {comments: true});
this.block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
'<block type="empty_block"/>'
), this.workspace);
});
test('Text', function() {
this.block.setCommentText('test text');
var xml = Blockly.Xml.blockToDom(this.block);
var commentXml = xml.firstChild;
chai.assert.equal(commentXml.tagName, 'comment');
chai.assert.equal(commentXml.innerHTML, 'test text');
});
test('No Text', function() {
var xml = Blockly.Xml.blockToDom(this.block);
chai.assert.isNull(xml.firstChild);
});
test('Empty Text', function() {
this.block.setCommentText('');
var xml = Blockly.Xml.blockToDom(this.block);
chai.assert.isNull(xml.firstChild);
});
test('Size', function() {
this.block.setCommentText('test text');
this.block.getCommentIcon().setBubbleSize(100, 200);
var xml = Blockly.Xml.blockToDom(this.block);
var commentXml = xml.firstChild;
chai.assert.equal(commentXml.tagName, 'comment');
chai.assert.equal(commentXml.getAttribute('w'), 100);
chai.assert.equal(commentXml.getAttribute('h'), 200);
});
test('Pinned True', function() {
this.block.setCommentText('test text');
this.block.getCommentIcon().setVisible(true);
var xml = Blockly.Xml.blockToDom(this.block);
var commentXml = xml.firstChild;
chai.assert.equal(commentXml.tagName, 'comment');
chai.assert.equal(commentXml.getAttribute('pinned'), 'true');
});
test('Pinned False', function() {
this.block.setCommentText('test text');
var xml = Blockly.Xml.blockToDom(this.block);
var commentXml = xml.firstChild;
chai.assert.equal(commentXml.tagName, 'comment');
chai.assert.equal(commentXml.getAttribute('pinned'), 'false');
});
});
});
});
suite('Deserialization', function() {
setup(function() {
this.workspace = new Blockly.Workspace();
});
teardown(function() {
this.workspace.dispose();
});
suite('Dynamic Category Blocks', function() {
test('Untyped Variables', function() {
Blockly.defineBlocksWithJsonArray([{
@@ -424,5 +515,196 @@ suite('XML', function() {
}
});
});
suite('Comments', function() {
suite('Headless', function() {
test('Text', function() {
var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
'<block type="empty_block">' +
' <comment>test text</comment>' +
'</block>'
), this.workspace);
chai.assert.equal(block.getCommentText(), 'test text');
});
test('No Text', function() {
var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
'<block type="empty_block">' +
' <comment></comment>' +
'</block>'
), this.workspace);
chai.assert.equal(block.getCommentText(), '');
});
test('Size', function() {
var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
'<block type="empty_block">' +
' <comment w="100" h="200">test text</comment>' +
'</block>'
), this.workspace);
chai.assert.deepEqual(block.commentModel.size,
{width: 100, height: 200});
});
test('Pinned True', function() {
var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
'<block type="empty_block">' +
' <comment pinned="true">test text</comment>' +
'</block>'
), this.workspace);
chai.assert.isTrue(block.commentModel.pinned);
});
test('Pinned False', function() {
var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
'<block type="empty_block">' +
' <comment pinned="false">test text</comment>' +
'</block>'
), this.workspace);
chai.assert.isFalse(block.commentModel.pinned);
});
test('Pinned Undefined', function() {
var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
'<block type="empty_block">' +
' <comment>test text</comment>' +
'</block>'
), this.workspace);
chai.assert.isFalse(block.commentModel.pinned);
});
});
suite('Rendered', function() {
setup(function() {
// Let the parent teardown dispose of it.
this.workspace = Blockly.inject('blocklyDiv', {comments: true});
});
test('Text', function() {
var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
'<block type="empty_block">' +
' <comment>test text</comment>' +
'</block>'
), this.workspace);
chai.assert.equal(block.getCommentText(), 'test text');
chai.assert.isNotNull(block.getCommentIcon());
});
test('No Text', function() {
var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
'<block type="empty_block">' +
' <comment></comment>' +
'</block>'
), this.workspace);
chai.assert.equal(block.getCommentText(), '');
chai.assert.isNotNull(block.getCommentIcon());
});
test('Size', function() {
var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
'<block type="empty_block">' +
' <comment w="100" h="200">test text</comment>' +
'</block>'
), this.workspace);
chai.assert.deepEqual(block.commentModel.size,
{width: 100, height: 200});
chai.assert.isNotNull(block.getCommentIcon());
chai.assert.deepEqual(block.getCommentIcon().getBubbleSize(),
{width: 100, height: 200});
});
suite('Pinned', function() {
setup(function() {
this.clock = sinon.useFakeTimers();
});
teardown(function() {
this.clock.restore();
});
test('Pinned True', function() {
var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
'<block type="empty_block">' +
' <comment pinned="true">test text</comment>' +
'</block>'
), this.workspace);
this.clock.tick(1);
chai.assert.isTrue(block.commentModel.pinned);
chai.assert.isNotNull(block.getCommentIcon());
chai.assert.isTrue(block.getCommentIcon().isVisible());
});
test('Pinned False', function() {
var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
'<block type="empty_block">' +
' <comment pinned="false">test text</comment>' +
'</block>'
), this.workspace);
this.clock.tick(1);
chai.assert.isFalse(block.commentModel.pinned);
chai.assert.isNotNull(block.getCommentIcon());
chai.assert.isFalse(block.getCommentIcon().isVisible());
});
test('Pinned Undefined', function() {
var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
'<block type="empty_block">' +
' <comment>test text</comment>' +
'</block>'
), this.workspace);
this.clock.tick(1);
chai.assert.isFalse(block.commentModel.pinned);
chai.assert.isNotNull(block.getCommentIcon());
chai.assert.isFalse(block.getCommentIcon().isVisible());
});
});
});
});
});
suite('Round Tripping', function() {
setup(function() {
var options = {
comments: true
};
this.renderedWorkspace = Blockly.inject('blocklyDiv', options);
this.headlessWorkspace = new Blockly.Workspace(options);
});
teardown(function() {
this.renderedWorkspace.dispose();
this.headlessWorkspace.dispose();
});
suite('Rendered -> XML -> Headless -> XML', function() {
setup(function() {
this.assertRoundTrip = function() {
var renderedXml = Blockly.Xml.workspaceToDom(this.renderedWorkspace);
Blockly.Xml.domToWorkspace(renderedXml, this.headlessWorkspace);
var headlessXml = Blockly.Xml.workspaceToDom(this.headlessWorkspace);
var renderedText = Blockly.Xml.domToText(renderedXml);
var headlessText = Blockly.Xml.domToText(headlessXml);
chai.assert.equal(headlessText, renderedText);
};
});
test('Comment', function() {
var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
'<block type="empty_block"/>'
), this.renderedWorkspace);
block.setCommentText('test text');
block.getCommentIcon().setBubbleSize(100, 100);
block.getCommentIcon().setVisible(true);
this.assertRoundTrip();
});
});
suite('Headless -> XML -> Rendered -> XML', function() {
setup(function() {
this.assertRoundTrip = function() {
var headlessXml = Blockly.Xml.workspaceToDom(this.headlessWorkspace);
Blockly.Xml.domToWorkspace(headlessXml, this.renderedWorkspace);
var renderedXml = Blockly.Xml.workspaceToDom(this.renderedWorkspace);
var renderedText = Blockly.Xml.domToText(renderedXml);
var headlessText = Blockly.Xml.domToText(headlessXml);
chai.assert.equal(renderedText, headlessText);
};
});
test('Comment', function() {
var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
'<block type="empty_block"/>'
), this.headlessWorkspace);
block.setCommentText('test text');
block.commentModel.size = new Blockly.utils.Size(100, 100);
block.commentModel.pinned = true;
this.assertRoundTrip();
});
});
});
});