fix: create and delete events, and the trashcan (#5425)

* fix: create and delete events with JSON serialization

* fix: trashcan with JSON serialization

* fix: build

* fix: tests

* fix: PR comments

* fix: types

* fix: tests
This commit is contained in:
Beka Westberg
2021-09-09 20:48:38 +00:00
committed by alschmiedt
parent 17f9f4f689
commit 84514efb09
9 changed files with 274 additions and 201 deletions

View File

@@ -18,9 +18,9 @@ const Block = goog.requireType('Blockly.Block');
const BlockBase = goog.require('Blockly.Events.BlockBase');
const Events = goog.require('Blockly.Events');
const Xml = goog.require('Blockly.Xml');
const blocks = goog.require('Blockly.serialization.blocks');
const object = goog.require('Blockly.utils.object');
const registry = goog.require('Blockly.registry');
const xml = goog.require('Blockly.utils.xml');
/**
@@ -40,12 +40,15 @@ const BlockCreate = function(opt_block) {
this.recordUndo = false;
}
if (opt_block.workspace.rendered) {
this.xml = Xml.blockToDomWithXY(opt_block);
} else {
this.xml = Xml.blockToDom(opt_block);
}
this.xml = Xml.blockToDomWithXY(opt_block);
this.ids = Events.getDescendantIds(opt_block);
/**
* JSON representation of the block that was just created.
* @type {!blocks.State}
*/
this.json = /** @type {!blocks.State} */ (blocks.save(
opt_block, {addCoordinates: true}));
};
object.inherits(BlockCreate, BlockBase);
@@ -63,6 +66,7 @@ BlockCreate.prototype.toJson = function() {
const json = BlockCreate.superClass_.toJson.call(this);
json['xml'] = Xml.domToText(this.xml);
json['ids'] = this.ids;
json['json'] = this.json;
if (!this.recordUndo) {
json['recordUndo'] = this.recordUndo;
}
@@ -77,6 +81,7 @@ BlockCreate.prototype.fromJson = function(json) {
BlockCreate.superClass_.fromJson.call(this, json);
this.xml = Xml.textToDom(json['xml']);
this.ids = json['ids'];
this.json = /** @type {!blocks.State} */ (json['json']);
if (json['recordUndo'] !== undefined) {
this.recordUndo = json['recordUndo'];
}
@@ -89,9 +94,7 @@ BlockCreate.prototype.fromJson = function(json) {
BlockCreate.prototype.run = function(forward) {
const workspace = this.getEventWorkspace_();
if (forward) {
const xmlEl = xml.createElement('xml');
xmlEl.appendChild(this.xml);
Xml.domToWorkspace(xmlEl, workspace);
blocks.load(this.json, workspace);
} else {
for (let i = 0; i < this.ids.length; i++) {
const id = this.ids[i];

View File

@@ -18,9 +18,9 @@ const Block = goog.requireType('Blockly.Block');
const BlockBase = goog.require('Blockly.Events.BlockBase');
const Events = goog.require('Blockly.Events');
const Xml = goog.require('Blockly.Xml');
const blocks = goog.require('Blockly.serialization.blocks');
const object = goog.require('Blockly.utils.object');
const registry = goog.require('Blockly.registry');
const xml = goog.require('Blockly.utils.xml');
/**
@@ -43,12 +43,21 @@ const BlockDelete = function(opt_block) {
this.recordUndo = false;
}
if (opt_block.workspace.rendered) {
this.oldXml = Xml.blockToDomWithXY(opt_block);
} else {
this.oldXml = Xml.blockToDom(opt_block);
}
this.oldXml = Xml.blockToDomWithXY(opt_block);
this.ids = Events.getDescendantIds(opt_block);
/**
* Was the block that was just deleted a shadow?
* @type {boolean}
*/
this.wasShadow = opt_block.isShadow();
/**
* JSON representation of the block that was just deleted.
* @type {!blocks.State}
*/
this.oldJson = /** @type {!blocks.State} */ (blocks.save(
opt_block, {addCoordinates: true}));
};
object.inherits(BlockDelete, BlockBase);
@@ -66,6 +75,8 @@ BlockDelete.prototype.toJson = function() {
const json = BlockDelete.superClass_.toJson.call(this);
json['oldXml'] = Xml.domToText(this.oldXml);
json['ids'] = this.ids;
json['wasShadow'] = this.wasShadow;
json['oldJson'] = this.oldJson;
if (!this.recordUndo) {
json['recordUndo'] = this.recordUndo;
}
@@ -80,6 +91,9 @@ BlockDelete.prototype.fromJson = function(json) {
BlockDelete.superClass_.fromJson.call(this, json);
this.oldXml = Xml.textToDom(json['oldXml']);
this.ids = json['ids'];
this.wasShadow =
json['wasShadow'] || this.oldXml.tagName.toLowerCase() == 'shadow';
this.oldJson = /** @type {!blocks.State} */ (json['oldJson']);
if (json['recordUndo'] !== undefined) {
this.recordUndo = json['recordUndo'];
}
@@ -103,9 +117,7 @@ BlockDelete.prototype.run = function(forward) {
}
}
} else {
const xmlEl = xml.createElement('xml');
xmlEl.appendChild(this.oldXml);
Xml.domToWorkspace(xmlEl, workspace);
blocks.load(this.oldJson, workspace);
}
};

View File

@@ -36,7 +36,8 @@ const Size = goog.require('Blockly.utils.Size');
const Svg = goog.require('Blockly.utils.Svg');
/* eslint-disable-next-line no-unused-vars */
const WorkspaceSvg = goog.requireType('Blockly.WorkspaceSvg');
const Xml = goog.require('Blockly.Xml');
/* eslint-disable-next-line no-unused-vars */
const blocks = goog.requireType('Blockly.serialization.blocks');
const browserEvents = goog.require('Blockly.browserEvents');
const dom = goog.require('Blockly.utils.dom');
const internalConstants = goog.require('Blockly.internalConstants');
@@ -73,7 +74,7 @@ const Trashcan = function(workspace) {
this.id = 'trashcan';
/**
* A list of XML (stored as strings) representing blocks in the trashcan.
* A list of JSON (stored as strings) representing blocks in the trashcan.
* @type {!Array<string>}
* @private
*/
@@ -393,8 +394,10 @@ Trashcan.prototype.openFlyout = function() {
if (this.contentsIsOpen()) {
return;
}
const xml = this.contents_.map(Xml.textToDom);
this.flyout.show(xml);
const contents = this.contents_.map(function(string) {
return JSON.parse(string);
});
this.flyout.show(contents);
this.fireUiEvent_(true);
};
@@ -669,14 +672,12 @@ Trashcan.prototype.onDelete_ = function(event) {
if (this.workspace_.options.maxTrashcanContents <= 0) {
return;
}
// Must check that the tagName exists since oldXml can be a DocumentFragment.
if (event.type == Events.BLOCK_DELETE && event.oldXml.tagName &&
event.oldXml.tagName.toLowerCase() != 'shadow') {
const cleanedXML = this.cleanBlockXML_(event.oldXml);
if (this.contents_.indexOf(cleanedXML) != -1) {
if (event.type == Events.BLOCK_DELETE && !event.wasShadow) {
const cleanedJson = this.cleanBlockJson_(event.oldJson);
if (this.contents_.indexOf(cleanedJson) != -1) {
return;
}
this.contents_.unshift(cleanedXML);
this.contents_.unshift(cleanedJson);
while (this.contents_.length >
this.workspace_.options.maxTrashcanContents) {
this.contents_.pop();
@@ -687,52 +688,51 @@ Trashcan.prototype.onDelete_ = function(event) {
};
/**
* Converts XML representing a block into text that can be stored in the
* content array.
* @param {!Element} xml An XML tree defining the block and any
* connected child blocks.
* @return {string} Text representing the XML tree, cleaned of all unnecessary
* attributes.
* Converts JSON representing a block into text that can be stored in the
* content array.
* @param {!blocks.State} json A JSON representation of
* a block's state.
* @return {string} Text representing the JSON, cleaned of all unnecessary
* attributes.
* @private
*/
Trashcan.prototype.cleanBlockXML_ = function(xml) {
const xmlBlock = xml.cloneNode(true);
let node = xmlBlock;
while (node) {
// Things like text inside tags are still treated as nodes, but they
// don't have attributes (or the removeAttribute function) so we can
// skip removing attributes from them.
if (node.removeAttribute) {
node.removeAttribute('x');
node.removeAttribute('y');
node.removeAttribute('id');
node.removeAttribute('disabled');
if (node.nodeName == 'comment') { // Future proof just in case.
node.removeAttribute('h');
node.removeAttribute('w');
node.removeAttribute('pinned');
}
Trashcan.prototype.cleanBlockJson_ = function(json) {
// Create a deep copy.
json = /** @type {!blocks.State} */(JSON.parse(JSON.stringify(json)));
function cleanRec(json) {
if (!json) {
return;
}
// Try to go down the tree
let nextNode = node.firstChild || node.nextSibling;
// If we can't go down, try to go back up the tree.
if (!nextNode) {
nextNode = node.parentNode;
while (nextNode) {
// We are valid again!
if (nextNode.nextSibling) {
nextNode = nextNode.nextSibling;
break;
}
// Try going up again. If parentNode is null that means we have
// reached the top, and we will break out of both loops.
nextNode = nextNode.parentNode;
}
delete json['id'];
delete json['x'];
delete json['y'];
delete json['enabled'];
if (json['icons'] && json['icons']['comment']) {
const comment = json['icons']['comment'];
delete comment['height'];
delete comment['width'];
delete comment['pinned'];
}
const inputs = json['inputs'];
for (var name in inputs) {
const input = inputs[name];
cleanRec(input['block']);
cleanRec(input['shadow']);
}
if (json['next']) {
const next = json['next'];
cleanRec(next['block']);
cleanRec(next['shadow']);
}
node = nextNode;
}
return Xml.domToText(xmlBlock);
cleanRec(json);
json['kind'] = 'BLOCK';
return JSON.stringify(json);
};
exports = Trashcan;

View File

@@ -40,8 +40,8 @@ goog.addDependency('../../core/events/events.js', ['Blockly.Events'], ['Blockly.
goog.addDependency('../../core/events/events_abstract.js', ['Blockly.Events.Abstract'], ['Blockly.Events'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../core/events/events_block_base.js', ['Blockly.Events.BlockBase'], ['Blockly.Events.Abstract', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../core/events/events_block_change.js', ['Blockly.Events.BlockChange'], ['Blockly.Events', 'Blockly.Xml', 'Blockly.registry', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../core/events/events_block_create.js', ['Blockly.Events.BlockCreate'], ['Blockly.Events', 'Blockly.Events.BlockBase', 'Blockly.Xml', 'Blockly.registry', 'Blockly.utils.object', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../core/events/events_block_delete.js', ['Blockly.Events.BlockDelete'], ['Blockly.Events', 'Blockly.Events.BlockBase', 'Blockly.Xml', 'Blockly.registry', 'Blockly.utils.object', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../core/events/events_block_create.js', ['Blockly.Events.BlockCreate'], ['Blockly.Events', 'Blockly.Events.BlockBase', 'Blockly.Xml', 'Blockly.registry', 'Blockly.serialization.blocks', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../core/events/events_block_delete.js', ['Blockly.Events.BlockDelete'], ['Blockly.Events', 'Blockly.Events.BlockBase', 'Blockly.Xml', 'Blockly.registry', 'Blockly.serialization.blocks', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../core/events/events_block_drag.js', ['Blockly.Events.BlockDrag'], ['Blockly.Events', 'Blockly.Events.UiBase', 'Blockly.registry', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../core/events/events_block_move.js', ['Blockly.Events.BlockMove'], ['Blockly.Events', 'Blockly.Events.BlockBase', 'Blockly.connectionTypes', 'Blockly.registry', 'Blockly.utils.Coordinate', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../core/events/events_bubble_open.js', ['Blockly.Events.BubbleOpen'], ['Blockly.Events', 'Blockly.Events.UiBase', 'Blockly.registry', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'});
@@ -217,7 +217,7 @@ goog.addDependency('../../core/toolbox/toolbox_item.js', ['Blockly.ToolboxItem']
goog.addDependency('../../core/tooltip.js', ['Blockly.Tooltip'], ['Blockly.browserEvents', 'Blockly.common', 'Blockly.utils.deprecation', 'Blockly.utils.string'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../core/touch.js', ['Blockly.Touch'], ['Blockly.internalConstants', 'Blockly.utils.global', 'Blockly.utils.string'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../core/touch_gesture.js', ['Blockly.TouchGesture'], ['Blockly.Gesture', 'Blockly.Touch', 'Blockly.browserEvents', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../core/trashcan.js', ['Blockly.Trashcan'], ['Blockly.ComponentManager', 'Blockly.DeleteArea', 'Blockly.Events', 'Blockly.Events.TrashcanOpen', 'Blockly.Options', 'Blockly.Xml', 'Blockly.browserEvents', 'Blockly.internalConstants', 'Blockly.registry', 'Blockly.uiPosition', 'Blockly.utils.Rect', 'Blockly.utils.Size', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.toolbox'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../core/trashcan.js', ['Blockly.Trashcan'], ['Blockly.ComponentManager', 'Blockly.DeleteArea', 'Blockly.Events', 'Blockly.Events.TrashcanOpen', 'Blockly.Options', 'Blockly.browserEvents', 'Blockly.internalConstants', 'Blockly.registry', 'Blockly.uiPosition', 'Blockly.utils.Rect', 'Blockly.utils.Size', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.toolbox'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../core/utils.js', ['Blockly.utils'], ['Blockly.Msg', 'Blockly.internalConstants', 'Blockly.utils.Coordinate', 'Blockly.utils.KeyCodes', 'Blockly.utils.Metrics', 'Blockly.utils.Rect', 'Blockly.utils.Size', 'Blockly.utils.Svg', 'Blockly.utils.aria', 'Blockly.utils.colour', 'Blockly.utils.deprecation', 'Blockly.utils.dom', 'Blockly.utils.global', 'Blockly.utils.idGenerator', 'Blockly.utils.math', 'Blockly.utils.object', 'Blockly.utils.string', 'Blockly.utils.style', 'Blockly.utils.svgPaths', 'Blockly.utils.toolbox', 'Blockly.utils.userAgent', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../core/utils/aria.js', ['Blockly.utils.aria'], [], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../core/utils/colour.js', ['Blockly.utils.colour'], ['Blockly.internalConstants'], {'lang': 'es6', 'module': 'goog'});

View File

@@ -40,8 +40,8 @@ goog.addDependency('../../core/events/events.js', ['Blockly.Events'], ['Blockly.
goog.addDependency('../../core/events/events_abstract.js', ['Blockly.Events.Abstract'], ['Blockly.Events'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../core/events/events_block_base.js', ['Blockly.Events.BlockBase'], ['Blockly.Events.Abstract', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../core/events/events_block_change.js', ['Blockly.Events.BlockChange'], ['Blockly.Events', 'Blockly.Xml', 'Blockly.registry', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../core/events/events_block_create.js', ['Blockly.Events.BlockCreate'], ['Blockly.Events', 'Blockly.Events.BlockBase', 'Blockly.Xml', 'Blockly.registry', 'Blockly.utils.object', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../core/events/events_block_delete.js', ['Blockly.Events.BlockDelete'], ['Blockly.Events', 'Blockly.Events.BlockBase', 'Blockly.Xml', 'Blockly.registry', 'Blockly.utils.object', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../core/events/events_block_create.js', ['Blockly.Events.BlockCreate'], ['Blockly.Events', 'Blockly.Events.BlockBase', 'Blockly.Xml', 'Blockly.registry', 'Blockly.serialization.blocks', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../core/events/events_block_delete.js', ['Blockly.Events.BlockDelete'], ['Blockly.Events', 'Blockly.Events.BlockBase', 'Blockly.Xml', 'Blockly.registry', 'Blockly.serialization.blocks', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../core/events/events_block_drag.js', ['Blockly.Events.BlockDrag'], ['Blockly.Events', 'Blockly.Events.UiBase', 'Blockly.registry', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../core/events/events_block_move.js', ['Blockly.Events.BlockMove'], ['Blockly.Events', 'Blockly.Events.BlockBase', 'Blockly.connectionTypes', 'Blockly.registry', 'Blockly.utils.Coordinate', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../core/events/events_bubble_open.js', ['Blockly.Events.BubbleOpen'], ['Blockly.Events', 'Blockly.Events.UiBase', 'Blockly.registry', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'});
@@ -217,7 +217,7 @@ goog.addDependency('../../core/toolbox/toolbox_item.js', ['Blockly.ToolboxItem']
goog.addDependency('../../core/tooltip.js', ['Blockly.Tooltip'], ['Blockly.browserEvents', 'Blockly.common', 'Blockly.utils.deprecation', 'Blockly.utils.string'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../core/touch.js', ['Blockly.Touch'], ['Blockly.internalConstants', 'Blockly.utils.global', 'Blockly.utils.string'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../core/touch_gesture.js', ['Blockly.TouchGesture'], ['Blockly.Gesture', 'Blockly.Touch', 'Blockly.browserEvents', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../core/trashcan.js', ['Blockly.Trashcan'], ['Blockly.ComponentManager', 'Blockly.DeleteArea', 'Blockly.Events', 'Blockly.Events.TrashcanOpen', 'Blockly.Options', 'Blockly.Xml', 'Blockly.browserEvents', 'Blockly.internalConstants', 'Blockly.registry', 'Blockly.uiPosition', 'Blockly.utils.Rect', 'Blockly.utils.Size', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.toolbox'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../core/trashcan.js', ['Blockly.Trashcan'], ['Blockly.ComponentManager', 'Blockly.DeleteArea', 'Blockly.Events', 'Blockly.Events.TrashcanOpen', 'Blockly.Options', 'Blockly.browserEvents', 'Blockly.internalConstants', 'Blockly.registry', 'Blockly.uiPosition', 'Blockly.utils.Rect', 'Blockly.utils.Size', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.toolbox'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../core/utils.js', ['Blockly.utils'], ['Blockly.Msg', 'Blockly.internalConstants', 'Blockly.utils.Coordinate', 'Blockly.utils.KeyCodes', 'Blockly.utils.Metrics', 'Blockly.utils.Rect', 'Blockly.utils.Size', 'Blockly.utils.Svg', 'Blockly.utils.aria', 'Blockly.utils.colour', 'Blockly.utils.deprecation', 'Blockly.utils.dom', 'Blockly.utils.global', 'Blockly.utils.idGenerator', 'Blockly.utils.math', 'Blockly.utils.object', 'Blockly.utils.string', 'Blockly.utils.style', 'Blockly.utils.svgPaths', 'Blockly.utils.toolbox', 'Blockly.utils.userAgent', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../core/utils/aria.js', ['Blockly.utils.aria'], [], {'lang': 'es6', 'module': 'goog'});
goog.addDependency('../../core/utils/colour.js', ['Blockly.utils.colour'], ['Blockly.internalConstants'], {'lang': 'es6', 'module': 'goog'});

View File

@@ -437,48 +437,118 @@ suite('Events', function() {
viewLeft: 0, scale: 1.2, oldScale: 1})},
];
var blockEventTestCases = [
{title: 'Block change', class: Blockly.Events.BlockChange,
{
title: 'Block change',
class: Blockly.Events.BlockChange,
getArgs: (thisObj) => [thisObj.block, 'collapsed', null, false, true],
getExpectedJson: (thisObj) => ({type: 'change',
blockId: thisObj.block.id, element: 'collapsed', oldValue: false,
newValue: true})},
{title: 'Block create', class: Blockly.Events.BlockCreate,
getExpectedJson: (thisObj) => ({
type: 'change',
blockId: thisObj.block.id,
element: 'collapsed',
oldValue: false,
newValue: true
})
},
{
title: 'Block create',
class: Blockly.Events.BlockCreate,
getArgs: (thisObj) => [thisObj.block],
getExpectedJson: (thisObj) => ({type: 'create',
getExpectedJson: (thisObj) => ({
type: 'create',
blockId: thisObj.block.id,
xml: '<block xmlns="https://developers.google.com/blockly/xml"' +
' type="simple_test_block" id="testBlockId1"></block>',
ids: [thisObj.block.id]})},
{title: 'Block create (shadow)', class: Blockly.Events.BlockCreate,
' type="simple_test_block" id="testBlockId1" x="0" y="0">' +
'</block>',
ids: [thisObj.block.id],
json: {
'type': 'simple_test_block',
'id': 'testBlockId1',
'x': 0,
'y': 0,
},
})
},
{
title: 'Block create (shadow)',
class: Blockly.Events.BlockCreate,
getArgs: (thisObj) => [thisObj.shadowBlock],
getExpectedJson: (thisObj) => ({type: 'create',
getExpectedJson: (thisObj) => ({
type: 'create',
blockId: thisObj.shadowBlock.id,
xml: '<shadow xmlns="https://developers.google.com/blockly/xml"' +
' type="simple_test_block" id="testBlockId2"></shadow>',
ids: [thisObj.shadowBlock.id], recordUndo: false})},
{title: 'Block delete', class: Blockly.Events.BlockDelete,
' type="simple_test_block" id="testBlockId2" x="0" y="0">' +
'</shadow>',
ids: [thisObj.shadowBlock.id],
json: {
'type': 'simple_test_block',
'id': 'testBlockId2',
'x': 0,
'y': 0,
},
recordUndo: false
})
},
{
title: 'Block delete',
class: Blockly.Events.BlockDelete,
getArgs: (thisObj) => [thisObj.block],
getExpectedJson: (thisObj) => ({type: 'delete',
getExpectedJson: (thisObj) => ({
type: 'delete',
blockId: thisObj.block.id,
oldXml: '<block xmlns="https://developers.google.com/blockly/xml"' +
' type="simple_test_block" id="testBlockId1"></block>',
ids: [thisObj.block.id]})},
{title: 'Block delete (shadow)', class: Blockly.Events.BlockDelete,
' type="simple_test_block" id="testBlockId1" x="0" y="0">' +
'</block>',
ids: [thisObj.block.id],
wasShadow: false,
oldJson: {
'type': 'simple_test_block',
'id': 'testBlockId1',
'x': 0,
'y': 0,
},
})
},
{
title: 'Block delete (shadow)',
class: Blockly.Events.BlockDelete,
getArgs: (thisObj) => [thisObj.shadowBlock],
getExpectedJson: (thisObj) => ({type: 'delete',
getExpectedJson: (thisObj) => ({
type: 'delete',
blockId: thisObj.shadowBlock.id,
oldXml: '<shadow xmlns="https://developers.google.com/blockly/xml"' +
' type="simple_test_block" id="testBlockId2"></shadow>',
ids: [thisObj.shadowBlock.id], recordUndo: false})},
' type="simple_test_block" id="testBlockId2" x="0" y="0">' +
'</shadow>',
ids: [thisObj.shadowBlock.id],
wasShadow: true,
oldJson: {
'type': 'simple_test_block',
'id': 'testBlockId2',
'x': 0,
'y': 0,
},
recordUndo: false
})
},
// TODO(#4577) Test serialization of move event coordinate properties.
{title: 'Block move', class: Blockly.Events.BlockMove,
{
title: 'Block move',
class: Blockly.Events.BlockMove,
getArgs: (thisObj) => [thisObj.block],
getExpectedJson: (thisObj) => ({type: 'move',
blockId: thisObj.block.id})},
{title: 'Block move (shadow)', class: Blockly.Events.BlockMove,
getExpectedJson: (thisObj) => ({
type: 'move',
blockId: thisObj.block.id
})
},
{
title: 'Block move (shadow)',
class: Blockly.Events.BlockMove,
getArgs: (thisObj) => [thisObj.shadowBlock],
getExpectedJson: (thisObj) => ({type: 'move',
blockId: thisObj.shadowBlock.id, recordUndo: false})},
getExpectedJson: (thisObj) => ({
type: 'move',
blockId: thisObj.shadowBlock.id,
recordUndo: false
})
},
];
var workspaceEventTestCases = [
{title: 'Finished Loading', class: Blockly.Events.FinishedLoading,

View File

@@ -689,7 +689,11 @@ suite('JSO Deserialization', function() {
Blockly.Blocks['test_block'] = {
init: function() { },
mutationToDom: function() { },
mutationToDom: function() {
var container = Blockly.utils.xml.createElement('mutation');
container.setAttribute('value', 'some value');
return container;
},
domToMutation: function(element) {
this.someProperty = element.getAttribute('value');

View File

@@ -481,9 +481,9 @@ function assertNthCallEventArgEquals(spy, n, instanceType, expectedProperties,
}
exports.assertNthCallEventArgEquals = assertNthCallEventArgEquals;
function defineStackBlock() {
function defineStackBlock(name = 'stack_block') {
Blockly.defineBlocksWithJsonArray([{
"type": "stack_block",
"type": name,
"message0": "",
"previousStatement": null,
"nextStatement": null
@@ -491,9 +491,9 @@ function defineStackBlock() {
}
exports.defineStackBlock = defineStackBlock;
function defineRowBlock() {
function defineRowBlock(name = 'row_block') {
Blockly.defineBlocksWithJsonArray([{
"type": "row_block",
"type": name,
"message0": "%1",
"args0": [
{
@@ -506,9 +506,9 @@ function defineRowBlock() {
}
exports.defineRowBlock = defineRowBlock;
function defineStatementBlock() {
function defineStatementBlock(name = 'statement_block') {
Blockly.defineBlocksWithJsonArray([{
"type": "statement_block",
"type": name,
"message0": "%1",
"args0": [
{
@@ -525,9 +525,9 @@ function defineStatementBlock() {
}
exports.defineStatementBlock = defineStatementBlock;
function defineBasicBlockWithField() {
function defineBasicBlockWithField(name = 'test_field_block') {
Blockly.defineBlocksWithJsonArray([{
"type": "test_field_block",
"type": name,
"message0": "%1",
"args0": [
{

View File

@@ -6,7 +6,7 @@
goog.module('Blockly.test.trashcan');
const {assertEventFired, assertEventNotFired, sharedTestSetup, sharedTestTeardown, simulateClick} = goog.require('Blockly.test.helpers');
const {assertEventFired, assertEventNotFired, defineBasicBlockWithField, defineRowBlock, defineStatementBlock, defineStackBlock, defineMutatorBlocks, sharedTestSetup, sharedTestTeardown, simulateClick} = goog.require('Blockly.test.helpers');
suite("Trashcan", function() {
@@ -15,14 +15,13 @@ suite("Trashcan", function() {
'<xml xmlns="https://developers.google.com/blockly/xml">' +
xmlString + '</xml>');
xml = xml.children[0];
var event = new Blockly.Events.BlockDelete();
event.oldXml = xml;
event.workspaceId = workspace.id;
var block = Blockly.Xml.domToBlock(xml, workspace);
var event = new Blockly.Events.BlockDelete(block);
Blockly.Events.fire(event);
}
function fireNonDeleteEvent(workspace, oldXml) {
var event = new Blockly.Events.Abstract();
event.type = 'dummy_type';
event.type = 'test_field_block';
event.workspaceId = workspace.id;
if (oldXml) {
event.oldXml = oldXml;
@@ -32,17 +31,27 @@ suite("Trashcan", function() {
setup(function() {
sharedTestSetup.call(this);
defineBasicBlockWithField();
defineRowBlock();
defineRowBlock('row_block2');
defineStatementBlock();
defineStatementBlock('statement_block2');
defineStackBlock();
defineStackBlock('stack_block2');
defineMutatorBlocks();
this.workspace = Blockly.inject('blocklyDiv',
{'trashcan': true, 'maxTrashcanContents': Infinity});
this.trashcan = this.workspace.trashcan;
});
teardown(function() {
sharedTestTeardown.call(this);
Blockly.Extensions.unregister('xml_mutator');
Blockly.Extensions.unregister('jso_mutator');
});
suite("Events", function() {
test("Delete", function() {
fireDeleteEvent(this.workspace, '<block type="dummy_type"/>');
fireDeleteEvent(this.workspace, '<block type="test_field_block"/>');
chai.assert.equal(this.trashcan.contents_.length, 1);
});
test("Non-Delete", function() {
@@ -52,7 +61,7 @@ suite("Trashcan", function() {
test("Non-Delete w/ oldXml", function() {
var xml = Blockly.Xml.textToDom(
'<xml xmlns="https://developers.google.com/blockly/xml">' +
' <block type="dummy_type"/>' +
' <block type="test_field_block"/>' +
'</xml>'
);
xml = xml.children[0];
@@ -60,7 +69,7 @@ suite("Trashcan", function() {
chai.assert.equal(this.trashcan.contents_.length, 0);
});
test("Shadow Delete", function() {
fireDeleteEvent(this.workspace, '<shadow type="dummy_type"/>');
fireDeleteEvent(this.workspace, '<shadow type="test_field_block"/>');
chai.assert.equal(this.trashcan.contents_.length, 0);
});
test("Click without contents - fires workspace click", function() {
@@ -73,7 +82,7 @@ suite("Trashcan", function() {
this.workspace.id, null);
});
test("Click with contents - fires trashcanOpen", function() {
fireDeleteEvent(this.workspace, '<block type="dummy_type"/>');
fireDeleteEvent(this.workspace, '<block type="test_field_block"/>');
chai.assert.equal(this.trashcan.contents_.length, 1);
// Stub flyout interaction.
var showFlyoutStub = sinon.stub(this.trashcan.flyout, "show");
@@ -107,56 +116,52 @@ suite("Trashcan", function() {
});
suite("Unique Contents", function() {
test("Simple", function() {
fireDeleteEvent(this.workspace, '<block type="dummy_type"/>');
fireDeleteEvent(this.workspace, '<block type="dummy_type"/>');
fireDeleteEvent(this.workspace, '<block type="test_field_block"/>');
fireDeleteEvent(this.workspace, '<block type="test_field_block"/>');
chai.assert.equal(this.trashcan.contents_.length, 1);
});
test("Different Coords", function() {
fireDeleteEvent(this.workspace, '<block type="dummy_type" x="10" y="10"/>');
fireDeleteEvent(this.workspace, '<block type="dummy_type" x="20" y="20"/>');
fireDeleteEvent(
this.workspace, '<block type="test_field_block" x="10" y="10"/>');
fireDeleteEvent(
this.workspace, '<block type="test_field_block" x="20" y="20"/>');
chai.assert.equal(this.trashcan.contents_.length, 1);
});
test("Different IDs", function() {
fireDeleteEvent(this.workspace, '<block type="dummy_type" id="id1"/>');
fireDeleteEvent(this.workspace, '<block type="dummy_type" id="id2"/>');
fireDeleteEvent(
this.workspace, '<block type="test_field_block" id="id1"/>');
fireDeleteEvent(
this.workspace, '<block type="test_field_block" id="id2"/>');
chai.assert.equal(this.trashcan.contents_.length, 1);
});
test("No Disabled - Disabled True", function() {
fireDeleteEvent(this.workspace, '<block type="dummy_type"/>');
fireDeleteEvent(this.workspace, '<block type="dummy_type" disabled="true"/>');
fireDeleteEvent(
this.workspace, '<block type="test_field_block"/>');
fireDeleteEvent(
this.workspace, '<block type="test_field_block" disabled="true"/>');
// Disabled tags get removed because disabled blocks aren't allowed to
// be dragged from flyouts. See #2239 and #3243.
chai.assert.equal(this.trashcan.contents_.length, 1);
});
test("No Editable - Editable False", function() {
fireDeleteEvent(this.workspace, '<block type="dummy_type"/>');
fireDeleteEvent(this.workspace, '<block type="dummy_type" editable="false"/>');
chai.assert.equal(this.trashcan.contents_.length, 2);
});
test("No Movable - Movable False", function() {
fireDeleteEvent(this.workspace, '<block type="dummy_type"/>');
fireDeleteEvent(this.workspace, '<block type="dummy_type" movable="false"/>');
chai.assert.equal(this.trashcan.contents_.length, 2);
});
test("Different Field Values", function() {
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
' <field name="dummy_name">dummy_value1</field>' +
'<block type="test_field_block">' +
' <field name="NAME">dummy_value1</field>' +
'</block>'
);
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
' <field name="dummy_name">dummy_value2</field>' +
'<block type="test_field_block">' +
' <field name="NAME">dummy_value2</field>' +
'</block>'
);
chai.assert.equal(this.trashcan.contents_.length, 2);
});
test("No Values - Values", function() {
fireDeleteEvent(this.workspace, '<block type="dummy_type"/>');
fireDeleteEvent(this.workspace, '<block type="row_block"/>');
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
' <value name="dummy_input">' +
' <block type="dummy_type"/>' +
'<block type="row_block">' +
' <value name="INPUT">' +
' <block type="row_block"/>' +
' </value>' +
'</block>'
);
@@ -164,27 +169,27 @@ suite("Trashcan", function() {
});
test("Different Value Blocks", function() {
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
' <value name="dummy_input">' +
' <block type="dummy_type1"/>' +
'<block type="row_block">' +
' <value name="INPUT">' +
' <block type="row_block"/>' +
' </value>' +
'</block>'
);
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
' <value name="dummy_input">' +
' <block type="dummy_type2"/>' +
'<block type="row_block">' +
' <value name="INPUT">' +
' <block type="row_block2"/>' +
' </value>' +
'</block>'
);
chai.assert.equal(this.trashcan.contents_.length, 2);
});
test("No Statements - Statements", function() {
fireDeleteEvent(this.workspace, '<block type="dummy_type"/>');
fireDeleteEvent(this.workspace, '<block type="statement_block"/>');
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
' <statement name="dummy_input">' +
' <block type="dummy_type"/>' +
'<block type="statement_block">' +
' <statement name="NAME">' +
' <block type="statement_block"/>' +
' </statement>' +
'</block>'
);
@@ -192,27 +197,27 @@ suite("Trashcan", function() {
});
test("Different Statement Blocks", function() {
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
' <statement name="dummy_input">' +
' <block type="dummy_type1"/>' +
'<block type="statement_block">' +
' <statement name="NAME">' +
' <block type="statement_block"/>' +
' </statement>' +
'</block>'
);
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
' <statement name="dummy_input">' +
' <block type="dummy_type2"/>' +
'<block type="test_field_block">' +
' <statement name="NAME">' +
' <block type="statement_block2"/>' +
' </statement>' +
'</block>'
);
chai.assert.equal(this.trashcan.contents_.length, 2);
});
test("No Next - Next", function() {
fireDeleteEvent(this.workspace, '<block type="dummy_type"/>');
fireDeleteEvent(this.workspace, '<block type="stack_block"/>');
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
'<block type="stack_block">' +
' <next>' +
' <block type="dummy_type"/>' +
' <block type="stack_block"/>' +
' </next>' +
'</block>'
);
@@ -220,25 +225,25 @@ suite("Trashcan", function() {
});
test("Different Next Blocks", function() {
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
'<block type="stack_block">' +
' <next>' +
' <block type="dummy_type1"/>' +
' <block type="stack_block"/>' +
' </next>' +
'</block>'
);
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
'<block type="stack_block">' +
' <next>' +
' <block type="dummy_type2"/>' +
' <block type="stack_block2"/>' +
' </next>' +
'</block>'
);
chai.assert.equal(this.trashcan.contents_.length, 2);
});
test("No Comment - Comment", function() {
fireDeleteEvent(this.workspace, '<block type="dummy_type"/>');
fireDeleteEvent(this.workspace, '<block type="test_field_block"/>');
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
'<block type="test_field_block">' +
' <comment>comment_text</comment>' +
'</block>'
);
@@ -246,12 +251,12 @@ suite("Trashcan", function() {
});
test("Different Comment Text", function() {
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
'<block type="test_field_block">' +
' <comment>comment_text1</comment>' +
'</block>'
);
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
'<block type="test_field_block">' +
' <comment>comment_text2</comment>' +
'</block>'
);
@@ -259,12 +264,12 @@ suite("Trashcan", function() {
});
test("Different Comment Size", function() {
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
'<block type="test_field_block">' +
' <comment h="10" w="10">comment_text</comment>' +
'</block>'
);
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
'<block type="test_field_block">' +
' <comment h="20" w="20">comment_text</comment>' +
'</block>'
);
@@ -273,36 +278,27 @@ suite("Trashcan", function() {
});
test("Different Comment Pinned", function() {
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
'<block type="test_field_block">' +
' <comment pinned="false">comment_text</comment>' +
'</block>'
);
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
'<block type="test_field_block">' +
' <comment pinned="true">comment_text</comment>' +
'</block>'
);
// pinned tags are removed b/c the blocks appear the same.
chai.assert.equal(this.trashcan.contents_.length, 1);
});
test("No Mutator - Mutator", function() {
fireDeleteEvent(this.workspace, '<block type="dummy_type"/>');
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
' <mutation dummy_attribute="dummy_value"></mutation>' +
'</block>'
);
chai.assert.equal(this.trashcan.contents_.length, 2);
});
test("Different Mutator", function() {
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
' <mutation dummy_attribute="dummy_value1"></mutation>' +
'<block type="xml_block">' +
' <mutation hasInput="true"></mutation>' +
'</block>'
);
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
' <mutation dummy_attribute="dummy_value2"></mutation>' +
'<block type="xml_block">' +
' <mutation hasInputt="false"></mutation>' +
'</block>'
);
chai.assert.equal(this.trashcan.contents_.length, 2);
@@ -312,22 +308,10 @@ suite("Trashcan", function() {
test("Max 0", function() {
this.workspace.options.maxTrashcanContents = 0;
fireDeleteEvent(this.workspace,
'<block type="dummy_type"/>'
'<block type="test_field_block"/>'
);
chai.assert.equal(this.trashcan.contents_.length, 0);
this.workspace.options.maxTrashcanContents = Infinity;
});
test("Last In First Out", function() {
this.workspace.options.maxTrashcanContents = 1;
fireDeleteEvent(this.workspace, '<block type="dummy_type1"/>');
fireDeleteEvent(this.workspace, '<block type="dummy_type2"/>');
chai.assert.equal(this.trashcan.contents_.length, 1);
chai.assert.equal(
Blockly.Xml.textToDom(this.trashcan.contents_[0])
.getAttribute('type'),
'dummy_type2'
);
this.workspace.options.maxTrashcanContents = Infinity;
});
});
});