diff --git a/core/block_events.js b/core/block_events.js
index 06bd0fc5f..03e80b26b 100644
--- a/core/block_events.js
+++ b/core/block_events.js
@@ -218,6 +218,10 @@ Blockly.Events.Create = function(opt_block) {
if (!opt_block) {
return; // Blank event to be populated by fromJson.
}
+ if (opt_block.isShadow()) {
+ // Moving shadow blocks is handled via disconnection.
+ this.recordUndo = false;
+ }
if (opt_block.workspace.rendered) {
this.xml = Blockly.Xml.blockToDomWithXY(opt_block);
@@ -302,6 +306,10 @@ Blockly.Events.Delete = function(opt_block) {
if (opt_block.getParent()) {
throw Error('Connected blocks cannot be deleted.');
}
+ if (opt_block.isShadow()) {
+ // Respawning shadow blocks is handled via disconnection.
+ this.recordUndo = false;
+ }
if (opt_block.workspace.rendered) {
this.oldXml = Blockly.Xml.blockToDomWithXY(opt_block);
@@ -380,6 +388,10 @@ Blockly.Events.Move = function(opt_block) {
if (!opt_block) {
return; // Blank event to be populated by fromJson.
}
+ if (opt_block.isShadow()) {
+ // Moving shadow blocks is handled via disconnection.
+ this.recordUndo = false;
+ }
var location = this.currentLocation_();
this.oldParentId = location.parentId;
diff --git a/core/connection.js b/core/connection.js
index 8974ed292..556fb69f9 100644
--- a/core/connection.js
+++ b/core/connection.js
@@ -111,7 +111,7 @@ Blockly.Connection.prototype.connect_ = function(childConnection) {
var orphanBlock = parentConnection.targetBlock();
var shadowDom = parentConnection.getShadowDom();
// Temporarily set the shadow DOM to null so it does not respawn.
- parentConnection.setShadowDom(null);
+ parentConnection.shadowDom_ = null;
// Displaced shadow blocks dissolve rather than reattaching or bumping.
if (orphanBlock.isShadow()) {
// Save the shadow block so that field values are preserved.
@@ -179,7 +179,7 @@ Blockly.Connection.prototype.connect_ = function(childConnection) {
}
}
// Restore the shadow DOM.
- parentConnection.setShadowDom(shadowDom);
+ parentConnection.shadowDom_ = shadowDom;
}
var event;
@@ -204,12 +204,11 @@ Blockly.Connection.prototype.dispose = function() {
// isConnected returns true for shadows and non-shadows.
if (this.isConnected()) {
+ // Destroy the attached shadow block & its children (if it exists).
this.setShadowDom(null);
+
var targetBlock = this.targetBlock();
- if (targetBlock.isShadow()) {
- // Destroy the attached shadow block & its children.
- targetBlock.dispose(false);
- } else {
+ if (targetBlock) {
// Disconnect the attached normal block.
targetBlock.unplug();
}
@@ -444,7 +443,10 @@ Blockly.Connection.prototype.disconnect = function() {
Blockly.Events.setGroup(true);
}
this.disconnectInternal_(parentBlock, childBlock);
- parentConnection.respawnShadow_();
+ if (!childBlock.isShadow()) {
+ // If we were disconnecting a shadow, no need to spawn a new one.
+ parentConnection.respawnShadow_();
+ }
if (!eventGroup) {
Blockly.Events.setGroup(false);
}
@@ -479,7 +481,7 @@ Blockly.Connection.prototype.disconnectInternal_ = function(parentBlock,
Blockly.Connection.prototype.respawnShadow_ = function() {
var parentBlock = this.getSourceBlock();
var shadow = this.getShadowDom();
- if (parentBlock.workspace && shadow && Blockly.Events.recordUndo) {
+ if (parentBlock.workspace && shadow) {
var blockShadow =
Blockly.Xml.domToBlock(shadow, parentBlock.workspace);
if (blockShadow.outputConnection) {
@@ -586,15 +588,23 @@ Blockly.Connection.prototype.getCheck = function() {
};
/**
- * Change a connection's shadow block.
+ * Changes the connection's shadow block.
* @param {Element} shadow DOM representation of a block or null.
*/
Blockly.Connection.prototype.setShadowDom = function(shadow) {
this.shadowDom_ = shadow;
+ var target = this.targetBlock();
+ if (!target) {
+ this.respawnShadow_();
+ } else if (target.isShadow()) {
+ // The disconnect from dispose will automatically generate the new shadow.
+ target.dispose(false);
+ this.respawnShadow_();
+ }
};
/**
- * Return a connection's shadow block.
+ * Returns the xml representation of the connection's shadow block.
* @return {Element} Shadow DOM representation of a block or null.
*/
Blockly.Connection.prototype.getShadowDom = function() {
diff --git a/core/input.js b/core/input.js
index 2b31c7315..40a454e31 100644
--- a/core/input.js
+++ b/core/input.js
@@ -245,6 +245,30 @@ Blockly.Input.prototype.setAlign = function(align) {
return this;
};
+/**
+ * Changes the connection's shadow block.
+ * @param {Element} shadow DOM representation of a block or null.
+ * @return {Blockly.Input} The input being modified (to allow chaining).
+ */
+Blockly.Input.prototype.setShadowDom = function(shadow) {
+ if (!this.connection) {
+ throw Error('This input does not have a connection.');
+ }
+ this.connection.setShadowDom(shadow);
+ return this;
+};
+
+/**
+ * Returns the xml representation of the connection's shadow block.
+ * @return {Element} Shadow DOM representation of a block or null.
+ */
+Blockly.Input.prototype.getShadowDom = function() {
+ if (!this.connection) {
+ throw Error('This input does not have a connection.');
+ }
+ return this.connection.getShadowDom();
+};
+
/**
* Initialize the fields on this input.
*/
diff --git a/core/rendered_connection.js b/core/rendered_connection.js
index cbf9994d4..cfb06580f 100644
--- a/core/rendered_connection.js
+++ b/core/rendered_connection.js
@@ -478,20 +478,18 @@ Blockly.RenderedConnection.prototype.disconnectInternal_ = function(parentBlock,
* @private
*/
Blockly.RenderedConnection.prototype.respawnShadow_ = function() {
+ Blockly.RenderedConnection.superClass_.respawnShadow_.call(this);
+ var blockShadow = this.targetBlock();
+ if (!blockShadow) {
+ // This connection must not have a shadowDom_.
+ return;
+ }
+ blockShadow.initSvg();
+ blockShadow.render(false);
+
var parentBlock = this.getSourceBlock();
- // Respawn the shadow block if there is one.
- var shadow = this.getShadowDom();
- if (parentBlock.workspace && shadow && Blockly.Events.recordUndo) {
- Blockly.RenderedConnection.superClass_.respawnShadow_.call(this);
- var blockShadow = this.targetBlock();
- if (!blockShadow) {
- throw Error('Couldn\'t respawn the shadow block that should exist here.');
- }
- blockShadow.initSvg();
- blockShadow.render(false);
- if (parentBlock.rendered) {
- parentBlock.render();
- }
+ if (parentBlock.rendered) {
+ parentBlock.render();
}
};
diff --git a/core/xml.js b/core/xml.js
index 899b3c64a..ae245b864 100644
--- a/core/xml.js
+++ b/core/xml.js
@@ -650,10 +650,6 @@ Blockly.Xml.domToBlockHeadless_ = function(xmlBlock, workspace) {
}
}
}
- // Use the shadow block if there is no child block.
- if (!childBlockElement && childShadowElement) {
- childBlockElement = childShadowElement;
- }
var name = xmlChild.getAttribute('name');
var xmlChildElement = /** @type {!Element} */ (xmlChild);
@@ -708,9 +704,6 @@ Blockly.Xml.domToBlockHeadless_ = function(xmlBlock, workspace) {
prototypeName);
break;
}
- if (childShadowElement) {
- input.connection.setShadowDom(childShadowElement);
- }
if (childBlockElement) {
blockChild = Blockly.Xml.domToBlockHeadless_(childBlockElement,
workspace);
@@ -723,11 +716,12 @@ Blockly.Xml.domToBlockHeadless_ = function(xmlBlock, workspace) {
'Child block does not have output or previous statement.');
}
}
+ // Set shadow after so we don't create a shadow we delete immediately.
+ if (childShadowElement) {
+ input.connection.setShadowDom(childShadowElement);
+ }
break;
case 'next':
- if (childShadowElement && block.nextConnection) {
- block.nextConnection.setShadowDom(childShadowElement);
- }
if (childBlockElement) {
if (!block.nextConnection) {
throw TypeError('Next statement does not exist.');
@@ -743,6 +737,10 @@ Blockly.Xml.domToBlockHeadless_ = function(xmlBlock, workspace) {
}
block.nextConnection.connect(blockChild.previousConnection);
}
+ // Set shadow after so we don't create a shadow we delete immediately.
+ if (childShadowElement && block.nextConnection) {
+ block.nextConnection.setShadowDom(childShadowElement);
+ }
break;
default:
// Unknown tag; ignore. Same principle as HTML parsers.
diff --git a/tests/mocha/connection_test.js b/tests/mocha/connection_test.js
index 48bee2513..73882b90b 100644
--- a/tests/mocha/connection_test.js
+++ b/tests/mocha/connection_test.js
@@ -18,9 +18,11 @@ suite('Connection', function() {
return connection;
};
});
+
teardown(function() {
sharedTestTeardown.call(this);
});
+
test('Deprecated - canConnectWithReason passes', function() {
var deprecateWarnSpy = createDeprecationWarningStub();
var conn1 = this.createConnection(Blockly.PREVIOUS_STATEMENT);
@@ -30,6 +32,7 @@ suite('Connection', function() {
assertSingleDeprecationWarningCall(deprecateWarnSpy,
'Connection.prototype.canConnectWithReason');
});
+
test('Deprecated - canConnectWithReason fails', function() {
var deprecateWarnSpy = createDeprecationWarningStub();
var conn1 = this.createConnection(Blockly.PREVIOUS_STATEMENT);
@@ -39,6 +42,7 @@ suite('Connection', function() {
assertSingleDeprecationWarningCall(deprecateWarnSpy,
'Connection.prototype.canConnectWithReason');
});
+
test('Deprecated - checkConnection passes', function() {
var deprecateWarnSpy = createDeprecationWarningStub();
var conn1 = this.createConnection(Blockly.PREVIOUS_STATEMENT);
@@ -49,6 +53,7 @@ suite('Connection', function() {
assertSingleDeprecationWarningCall(deprecateWarnSpy,
'Connection.prototype.checkConnection');
});
+
test('Deprecated - checkConnection fails', function() {
var deprecateWarnSpy = createDeprecationWarningStub();
var conn1 = this.createConnection(Blockly.PREVIOUS_STATEMENT);
@@ -59,4 +64,724 @@ suite('Connection', function() {
assertSingleDeprecationWarningCall(deprecateWarnSpy,
'Connection.prototype.checkConnection');
});
+
+ suite('Set Shadow Dom', function() {
+
+ function assertBlockMatches(block, isShadow, opt_id) {
+ chai.assert.equal(block.isShadow(), isShadow,
+ `expected block ${block.id} to ${isShadow ? '' : 'not'} be a shadow`);
+ if (opt_id) {
+ chai.assert.equal(block.id, opt_id);
+ }
+ }
+
+ function assertInputHasBlock(parent, inputName, isShadow, opt_name) {
+ var block = parent.getInputTargetBlock(inputName);
+ chai.assert.exists(block,
+ `expected block ${opt_name || ''} to be attached to ${inputName}`);
+ assertBlockMatches(block, isShadow, opt_name);
+ }
+
+ function assertNextHasBlock(parent, isShadow, opt_name) {
+ var block = parent.getNextBlock();
+ chai.assert.exists(block,
+ `expected block ${opt_name || ''} to be attached to next connection`);
+ assertBlockMatches(block, isShadow, opt_name);
+ }
+
+ function assertInputNotHasBlock(parent, inputName) {
+ var block = parent.getInputTargetBlock(inputName);
+ chai.assert.notExists(block,
+ `expected block ${block && block.id} to not be attached to ${inputName}`);
+ }
+
+ function assertNextNotHasBlock(parent) {
+ var block = parent.getNextBlock();
+ chai.assert.notExists(block,
+ `expected block ${block && block.id} to not be attached to next connection`);
+ }
+
+ var testSuites = [
+ {
+ title: 'Rendered',
+ createWorkspace: () => {
+ return Blockly.inject('blocklyDiv');
+ },
+ },
+ {
+ title: 'Headless',
+ createWorkspace: () => {
+ return new Blockly.Workspace();
+ },
+ }
+ ];
+
+ testSuites.forEach((testSuite) => {
+ // Create a suite for each suite.
+ suite(testSuite.title, function() {
+ setup(function() {
+ this.workspace = testSuite.createWorkspace();
+
+ Blockly.defineBlocksWithJsonArray([
+ {
+ "type": "stack_block",
+ "message0": "",
+ "previousStatement": null,
+ "nextStatement": null
+ },
+ {
+ "type": "row_block",
+ "message0": "%1",
+ "args0": [
+ {
+ "type": "input_value",
+ "name": "INPUT"
+ }
+ ],
+ "output": null
+ },
+ {
+ "type": "statement_block",
+ "message0": "%1",
+ "args0": [
+ {
+ "type": "input_statement",
+ "name": "STATEMENT"
+ }
+ ],
+ "previousStatement": null,
+ "nextStatement": null
+ }]);
+ });
+
+ teardown(function() {
+ workspaceTeardown.call(this, this.workspace);
+ delete Blockly.Blocks['stack_block'];
+ delete Blockly.Blocks['row_block'];
+ delete Blockly.Blocks['statement_block'];
+ });
+
+ suite('Add - No Block Connected', function() {
+ // These are defined separately in each suite.
+ function createRowBlock(workspace) {
+ var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
+ ''
+ ), workspace);
+ return block;
+ }
+
+ function createStatementBlock(workspace) {
+ var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
+ ''
+ ), workspace);
+ return block;
+ }
+
+ function createStackBlock(workspace) {
+ var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
+ ''
+ ), workspace);
+ return block;
+ }
+
+ test('Value', function() {
+ var parent = createRowBlock(this.workspace);
+ var xml = Blockly.Xml.textToDom(
+ ''
+ );
+ parent.getInput('INPUT').connection.setShadowDom(xml);
+ assertInputHasBlock(parent, 'INPUT', true);
+ });
+
+ test('Multiple Value', function() {
+ var parent = createRowBlock(this.workspace);
+ var xml = Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ );
+ parent.getInput('INPUT').connection.setShadowDom(xml);
+ assertInputHasBlock(parent, 'INPUT', true);
+ assertInputHasBlock(
+ parent.getInputTargetBlock('INPUT'), 'INPUT', true);
+ });
+
+ test('Statement', function() {
+ var parent = createStatementBlock(this.workspace);
+ var xml = Blockly.Xml.textToDom(
+ ''
+ );
+ parent.getInput('STATEMENT').connection.setShadowDom(xml);
+ assertInputHasBlock(parent, 'STATEMENT', true);
+ });
+
+ test('Multiple Statement', function() {
+ var parent = createStatementBlock(this.workspace);
+ var xml = Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ );
+ parent.getInput('STATEMENT').connection.setShadowDom(xml);
+ assertInputHasBlock(parent, 'STATEMENT', true);
+ assertInputHasBlock(
+ parent.getInputTargetBlock('STATEMENT'), 'STATEMENT', true);
+ });
+
+ test('Next', function() {
+ var parent = createStackBlock(this.workspace);
+ var xml = Blockly.Xml.textToDom(
+ ''
+ );
+ parent.nextConnection.setShadowDom(xml);
+ assertNextHasBlock(parent, true);
+ });
+
+ test('Multiple Next', function() {
+ var parent = createStackBlock(this.workspace);
+ var xml = Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ );
+ parent.nextConnection.setShadowDom(xml);
+ assertNextHasBlock(parent, true);
+ assertNextHasBlock(parent.getNextBlock(), true);
+ });
+ });
+
+ suite('Add - With Block Connected', function() {
+ // These are defined separately in each suite.
+ function createRowBlocks(workspace) {
+ var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ ), workspace);
+ return block;
+ }
+
+ function createStatementBlocks(workspace) {
+ var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ ), workspace);
+ return block;
+ }
+
+ function createStackBlocks(workspace) {
+ var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ ), workspace);
+ return block;
+ }
+
+ test('Value', function() {
+ var parent = createRowBlocks(this.workspace);
+ var xml = Blockly.Xml.textToDom(
+ ''
+ );
+ parent.getInput('INPUT').connection.setShadowDom(xml);
+ assertInputHasBlock(parent, 'INPUT', false);
+ parent.getInput('INPUT').connection.disconnect();
+ assertInputHasBlock(parent, 'INPUT', true);
+ });
+
+ test('Multiple Value', function() {
+ var parent = createRowBlocks(this.workspace);
+ var xml = Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ );
+ parent.getInput('INPUT').connection.setShadowDom(xml);
+ assertInputHasBlock(parent, 'INPUT', false);
+ assertInputNotHasBlock(parent.getInputTargetBlock('INPUT'), 'INPUT');
+ parent.getInput('INPUT').connection.disconnect();
+ assertInputHasBlock(parent, 'INPUT', true);
+ assertInputHasBlock(
+ parent.getInputTargetBlock('INPUT'), 'INPUT', true);
+ });
+
+ test('Statement', function() {
+ var parent = createStatementBlocks(this.workspace);
+ var xml = Blockly.Xml.textToDom(
+ ''
+ );
+ parent.getInput('STATEMENT').connection.setShadowDom(xml);
+ assertInputHasBlock(parent, 'STATEMENT', false);
+ parent.getInput('STATEMENT').connection.disconnect();
+ assertInputHasBlock(parent, 'STATEMENT', true);
+ });
+
+ test('Multiple Statement', function() {
+ var parent = createStatementBlocks(this.workspace);
+ var xml = Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ );
+ parent.getInput('STATEMENT').connection.setShadowDom(xml);
+ assertInputHasBlock(parent, 'STATEMENT', false);
+ assertInputNotHasBlock(
+ parent.getInputTargetBlock('STATEMENT'), 'STATEMENT');
+ parent.getInput('STATEMENT').connection.disconnect();
+ assertInputHasBlock(parent, 'STATEMENT', true);
+ assertInputHasBlock(
+ parent.getInputTargetBlock('STATEMENT'), 'STATEMENT', true);
+ });
+
+ test('Next', function() {
+ var parent = createStackBlocks(this.workspace);
+ var xml = Blockly.Xml.textToDom(
+ ''
+ );
+ parent.nextConnection.setShadowDom(xml);
+ assertNextHasBlock(parent, false);
+ parent.nextConnection.disconnect();
+ assertNextHasBlock(parent, true);
+ });
+
+ test('Multiple Next', function() {
+ var parent = createStackBlocks(this.workspace);
+ var xml = Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ );
+ parent.nextConnection.setShadowDom(xml);
+ assertNextHasBlock(parent, false);
+ assertNextNotHasBlock(parent.getNextBlock());
+ parent.nextConnection.disconnect();
+ assertNextHasBlock(parent, true);
+ assertNextHasBlock(parent.getNextBlock(), true);
+ });
+ });
+
+ suite('Add - With Shadow Connected', function() {
+ // These are defined separately in each suite.
+ function createRowBlock(workspace) {
+ var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
+ ''
+ ), workspace);
+ return block;
+ }
+
+ function createStatementBlock(workspace) {
+ var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
+ ''
+ ), workspace);
+ return block;
+ }
+
+ function createStackBlock(workspace) {
+ var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
+ ''
+ ), workspace);
+ return block;
+ }
+
+ test('Value', function() {
+ var parent = createRowBlock(this.workspace);
+ var xml = Blockly.Xml.textToDom(
+ ''
+ );
+ parent.getInput('INPUT').connection.setShadowDom(xml);
+ assertInputHasBlock(parent, 'INPUT', true, '1');
+ var xml = Blockly.Xml.textToDom(
+ ''
+ );
+ parent.getInput('INPUT').connection.setShadowDom(xml);
+ assertInputHasBlock(parent, 'INPUT', true, '2');
+ });
+
+ test('Multiple Value', function() {
+ var parent = createRowBlock(this.workspace);
+ var xml = Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ );
+ parent.getInput('INPUT').connection.setShadowDom(xml);
+ assertInputHasBlock(parent, 'INPUT', true, '1');
+ assertInputHasBlock(
+ parent.getInputTargetBlock('INPUT'), 'INPUT', true, 'a');
+ var xml = Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ );
+ parent.getInput('INPUT').connection.setShadowDom(xml);
+ assertInputHasBlock(parent, 'INPUT', true, '2');
+ assertInputHasBlock(
+ parent.getInputTargetBlock('INPUT'), 'INPUT', true, 'b');
+ });
+
+ test('Statement', function() {
+ var parent = createStatementBlock(this.workspace);
+ var xml = Blockly.Xml.textToDom(
+ ''
+ );
+ parent.getInput('STATEMENT').connection.setShadowDom(xml);
+ assertInputHasBlock(parent, 'STATEMENT', true, '1');
+ var xml = Blockly.Xml.textToDom(
+ ''
+ );
+ parent.getInput('STATEMENT').connection.setShadowDom(xml);
+ assertInputHasBlock(parent, 'STATEMENT', true, '2');
+ });
+
+ test('Multiple Statement', function() {
+ var parent = createStatementBlock(this.workspace);
+ var xml = Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ );
+ parent.getInput('STATEMENT').connection.setShadowDom(xml);
+ assertInputHasBlock(parent, 'STATEMENT', true, '1');
+ assertInputHasBlock(
+ parent.getInputTargetBlock('STATEMENT'), 'STATEMENT', true, 'a');
+ var xml = Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ );
+ parent.getInput('STATEMENT').connection.setShadowDom(xml);
+ assertInputHasBlock(parent, 'STATEMENT', true, '2');
+ assertInputHasBlock(
+ parent.getInputTargetBlock('STATEMENT'), 'STATEMENT', true, 'b');
+ });
+
+ test('Next', function() {
+ var parent = createStackBlock(this.workspace);
+ var xml = Blockly.Xml.textToDom(
+ ''
+ );
+ parent.nextConnection.setShadowDom(xml);
+ assertNextHasBlock(parent, true, '1');
+ var xml = Blockly.Xml.textToDom(
+ ''
+ );
+ parent.nextConnection.setShadowDom(xml);
+ assertNextHasBlock(parent, true, '2');
+ });
+
+ test('Multiple Next', function() {
+ var parent = createStackBlock(this.workspace);
+ var xml = Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ );
+ parent.nextConnection.setShadowDom(xml);
+ assertNextHasBlock(parent, true, '1');
+ assertNextHasBlock(parent.getNextBlock(), true, 'a');
+ var xml = Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ );
+ parent.nextConnection.setShadowDom(xml);
+ assertNextHasBlock(parent, true, '2');
+ assertNextHasBlock(parent.getNextBlock(), true, 'b');
+ });
+ });
+
+ suite('Remove - No Block Connected', function() {
+ // These are defined separately in each suite.
+ function createRowBlock(workspace) {
+ var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ ), workspace);
+ return block;
+ }
+
+ function createStatementBlock(workspace) {
+ var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ ), workspace);
+ return block;
+ }
+
+ function createStackBlock(workspace) {
+ var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ ), workspace);
+ return block;
+ }
+
+ test('Value', function() {
+ var parent = createRowBlock(this.workspace);
+ parent.getInput('INPUT').connection.setShadowDom(null);
+ assertInputNotHasBlock(parent, 'INPUT');
+ });
+
+ test('Statement', function() {
+ var parent = createStatementBlock(this.workspace);
+ parent.getInput('STATEMENT').connection.setShadowDom(null);
+ assertInputNotHasBlock(parent, 'STATMENT');
+ });
+
+ test('Next', function() {
+ var parent = createStackBlock(this.workspace);
+ parent.nextConnection.setShadowDom(null);
+ assertNextNotHasBlock(parent);
+ });
+ });
+
+ suite('Remove - Block Connected', function() {
+ // These are defined separately in each suite.
+ function createRowBlock(workspace) {
+ var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ ), workspace);
+ return block;
+ }
+
+ function createStatementBlock(workspace) {
+ var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ ), workspace);
+ return block;
+ }
+
+ function createStackBlock(workspace) {
+ var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ ), workspace);
+ return block;
+ }
+
+ test('Value', function() {
+ var parent = createRowBlock(this.workspace);
+ parent.getInput('INPUT').connection.setShadowDom(null);
+ assertInputHasBlock(parent, 'INPUT', false);
+ parent.getInput('INPUT').connection.disconnect();
+ assertInputNotHasBlock(parent, 'INPUT');
+ });
+
+ test('Statement', function() {
+ var parent = createStatementBlock(this.workspace);
+ parent.getInput('STATEMENT').connection.setShadowDom(null);
+ assertInputHasBlock(parent, 'STATEMENT', false);
+ parent.getInput('STATEMENT').connection.disconnect();
+ assertInputNotHasBlock(parent, 'STATEMENT');
+ });
+
+ test('Next', function() {
+ var parent = createStackBlock(this.workspace);
+ parent.nextConnection.setShadowDom(null);
+ assertNextHasBlock(parent, false);
+ parent.nextConnection.disconnect();
+ assertNextNotHasBlock(parent);
+ });
+ });
+
+ suite('Add - Connect & Disconnect - Remove', function() {
+ // These are defined separately in each suite.
+ function createRowBlock(workspace) {
+ var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
+ ''
+ ), workspace);
+ return block;
+ }
+
+ function createStatementBlock(workspace) {
+ var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
+ ''
+ ), workspace);
+ return block;
+ }
+
+ function createStackBlock(workspace) {
+ var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
+ ''
+ ), workspace);
+ return block;
+ }
+
+ test('Value', function() {
+ var parent = createRowBlock(this.workspace);
+ var xml = Blockly.Xml.textToDom(
+ ''
+ );
+ parent.getInput('INPUT').connection.setShadowDom(xml);
+ assertInputHasBlock(parent, 'INPUT', true);
+ var child = createRowBlock(this.workspace);
+ parent.getInput('INPUT').connection.connect(child.outputConnection);
+ assertInputHasBlock(parent, 'INPUT', false);
+ parent.getInput('INPUT').connection.disconnect();
+ assertInputHasBlock(parent, 'INPUT', true);
+ parent.getInput('INPUT').connection.setShadowDom(null);
+ assertInputNotHasBlock(parent, 'INPUT');
+ });
+
+ test('Multiple Value', function() {
+ var parent = createRowBlock(this.workspace);
+ var xml = Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ );
+ parent.getInput('INPUT').connection.setShadowDom(xml);
+ assertInputHasBlock(parent, 'INPUT', true);
+ assertInputHasBlock(
+ parent.getInputTargetBlock('INPUT'), 'INPUT', true);
+ var child = createRowBlock(this.workspace);
+ parent.getInput('INPUT').connection.connect(child.outputConnection);
+ assertInputHasBlock(parent, 'INPUT', false);
+ parent.getInput('INPUT').connection.disconnect();
+ assertInputHasBlock(parent, 'INPUT', true);
+ assertInputHasBlock(
+ parent.getInputTargetBlock('INPUT'), 'INPUT', true);
+ parent.getInput('INPUT').connection.setShadowDom(null);
+ assertInputNotHasBlock(parent, 'INPUT');
+ });
+
+ test('Statement', function() {
+ var parent = createStatementBlock(this.workspace);
+ var xml = Blockly.Xml.textToDom(
+ ''
+ );
+ parent.getInput('STATEMENT').connection.setShadowDom(xml);
+ assertInputHasBlock(parent, 'STATEMENT', true);
+ var child = createStatementBlock(this.workspace);
+ parent.getInput('STATEMENT').connection
+ .connect(child.previousConnection);
+ assertInputHasBlock(parent, 'STATEMENT', false);
+ parent.getInput('STATEMENT').connection.disconnect();
+ assertInputHasBlock(parent, 'STATEMENT', true);
+ parent.getInput('STATEMENT').connection.setShadowDom(null);
+ assertInputNotHasBlock(parent, 'STATEMENT');
+ });
+
+ test('Multiple Statement', function() {
+ var parent = createStatementBlock(this.workspace);
+ var xml = Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ );
+ parent.getInput('STATEMENT').connection.setShadowDom(xml);
+ assertInputHasBlock(parent, 'STATEMENT', true);
+ assertInputHasBlock(
+ parent.getInputTargetBlock('STATEMENT'), 'STATEMENT', true);
+ var child = createStatementBlock(this.workspace);
+ parent.getInput('STATEMENT').connection
+ .connect(child.previousConnection);
+ assertInputHasBlock(parent, 'STATEMENT', false);
+ parent.getInput('STATEMENT').connection.disconnect();
+ assertInputHasBlock(parent, 'STATEMENT', true);
+ assertInputHasBlock(
+ parent.getInputTargetBlock('STATEMENT'), 'STATEMENT', true);
+ parent.getInput('STATEMENT').connection.setShadowDom(null);
+ assertInputNotHasBlock(parent, 'STATEMENT');
+ });
+
+ test('Next', function() {
+ var parent = createStackBlock(this.workspace);
+ var xml = Blockly.Xml.textToDom(
+ ''
+ );
+ parent.nextConnection.setShadowDom(xml);
+ assertNextHasBlock(parent, true);
+ var child = createStatementBlock(this.workspace);
+ parent.nextConnection.connect(child.previousConnection);
+ assertNextHasBlock(parent, false);
+ parent.nextConnection.disconnect();
+ assertNextHasBlock(parent, true);
+ parent.nextConnection.setShadowDom(null);
+ assertNextNotHasBlock(parent);
+ });
+
+ test('Multiple Next', function() {
+ var parent = createStackBlock(this.workspace);
+ var xml = Blockly.Xml.textToDom(
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ );
+ parent.nextConnection.setShadowDom(xml);
+ assertNextHasBlock(parent, true);
+ assertNextHasBlock(parent.getNextBlock(), true);
+ var child = createStatementBlock(this.workspace);
+ parent.nextConnection.connect(child.previousConnection);
+ assertNextHasBlock(parent, false);
+ parent.nextConnection.disconnect();
+ assertNextHasBlock(parent, true);
+ assertNextHasBlock(parent.getNextBlock(), true);
+ parent.nextConnection.setShadowDom(null);
+ assertNextNotHasBlock(parent);
+ });
+ });
+ });
+ });
+ });
});
diff --git a/tests/mocha/event_test.js b/tests/mocha/event_test.js
index f265ce93c..67828f5c9 100644
--- a/tests/mocha/event_test.js
+++ b/tests/mocha/event_test.js
@@ -221,6 +221,148 @@ suite('Events', function() {
});
});
+ suite('With shadow blocks', function() {
+ setup(function() {
+ this.TEST_BLOCK_ID = 'test_block_id';
+ this.TEST_PARENT_ID = 'parent';
+ // genUid is expected to be called either once or twice in this suite.
+ this.genUidStub = createGenUidStubWithReturns(
+ [this.TEST_BLOCK_ID, this.TEST_PARENT_ID]);
+ this.block = createSimpleTestBlock(this.workspace);
+ this.block.setShadow(true);
+ });
+
+ test('Block base', function() {
+ var event = new Blockly.Events.BlockBase(this.block);
+ sinon.assert.calledOnce(this.genUidStub);
+ assertEventEquals(event, undefined,
+ this.workspace.id, this.TEST_BLOCK_ID,
+ {
+ 'varId': undefined,
+ 'recordUndo': true,
+ 'group': '',
+ });
+ });
+
+ test('Change', function() {
+ var event = new Blockly.Events.Change(
+ this.block, 'field', 'FIELD_NAME', 'old', 'new');
+ sinon.assert.calledOnce(this.genUidStub);
+ assertEventEquals(event, Blockly.Events.CHANGE,
+ this.workspace.id, this.TEST_BLOCK_ID,
+ {
+ 'varId': undefined,
+ 'element': 'field',
+ 'name': 'FIELD_NAME',
+ 'oldValue': 'old',
+ 'newValue': 'new',
+ 'recordUndo': true,
+ 'group': '',
+ });
+ });
+
+ test('Block change', function() {
+ var event = new Blockly.Events.BlockChange(
+ this.block, 'field', 'FIELD_NAME', 'old', 'new');
+ sinon.assert.calledOnce(this.genUidStub);
+ assertEventEquals(event, Blockly.Events.CHANGE,
+ this.workspace.id, this.TEST_BLOCK_ID,
+ {
+ 'varId': undefined,
+ 'element': 'field',
+ 'name': 'FIELD_NAME',
+ 'oldValue': 'old',
+ 'newValue': 'new',
+ 'recordUndo': true,
+ 'group': '',
+ });
+ });
+
+ test('Create', function() {
+ var event = new Blockly.Events.Create(this.block);
+ sinon.assert.calledOnce(this.genUidStub);
+ assertEventEquals(event, Blockly.Events.CREATE,
+ this.workspace.id, this.TEST_BLOCK_ID,
+ {
+ 'recordUndo': false,
+ 'group': '',
+ });
+ });
+
+ test('Block create', function() {
+ var event = new Blockly.Events.BlockCreate(this.block);
+ sinon.assert.calledOnce(this.genUidStub);
+ assertEventEquals(event, Blockly.Events.CREATE,
+ this.workspace.id, this.TEST_BLOCK_ID,
+ {
+ 'recordUndo': false,
+ 'group': '',
+ });
+ });
+
+ test('Delete', function() {
+ var event = new Blockly.Events.Delete(this.block);
+ sinon.assert.calledOnce(this.genUidStub);
+ assertEventEquals(event, Blockly.Events.DELETE,
+ this.workspace.id, this.TEST_BLOCK_ID,
+ {
+ 'recordUndo': false,
+ 'group': '',
+ });
+ });
+
+ test('Block delete', function() {
+ var event = new Blockly.Events.BlockDelete(this.block);
+ sinon.assert.calledOnce(this.genUidStub);
+ assertEventEquals(event, Blockly.Events.DELETE,
+ this.workspace.id, this.TEST_BLOCK_ID,
+ {
+ 'recordUndo': false,
+ 'group': '',
+ });
+ });
+
+ suite('Move', function() {
+ setup(function() {
+ this.parentBlock = createSimpleTestBlock(this.workspace);
+ this.block.parentBlock_ = this.parentBlock;
+ this.block.xy_ = new Blockly.utils.Coordinate(3, 4);
+ });
+
+ teardown(function() {
+ // This needs to be cleared, otherwise workspace.dispose will fail.
+ this.block.parentBlock_ = null;
+ });
+
+ test('Move', function() {
+ var event = new Blockly.Events.Move(this.block);
+ sinon.assert.calledTwice(this.genUidStub);
+ assertEventEquals(event, Blockly.Events.MOVE, this.workspace.id,
+ this.TEST_BLOCK_ID, {
+ 'oldParentId': this.TEST_PARENT_ID,
+ 'oldInputName': undefined,
+ 'oldCoordinate': undefined,
+ 'recordUndo': false,
+ 'group': ''
+ });
+ });
+
+ test('Block move', function() {
+ var event = new Blockly.Events.BlockMove(this.block);
+ sinon.assert.calledTwice(this.genUidStub);
+ assertEventEquals(event, Blockly.Events.MOVE, this.workspace.id,
+ this.TEST_BLOCK_ID,
+ {
+ 'oldParentId': this.TEST_PARENT_ID,
+ 'oldInputName': undefined,
+ 'oldCoordinate': undefined,
+ 'recordUndo': false,
+ 'group': ''
+ });
+ });
+ });
+ });
+
suite('With variable getter blocks', function() {
setup(function() {
this.genUidStub = createGenUidStubWithReturns(
diff --git a/tests/mocha/workspace_test.js b/tests/mocha/workspace_test.js
index c215dd8a4..401850868 100644
--- a/tests/mocha/workspace_test.js
+++ b/tests/mocha/workspace_test.js
@@ -640,369 +640,902 @@ function testAWorkspace() {
});
suite('Undo/Redo', function() {
- function createTwoVarsDifferentTypes(workspace) {
- workspace.createVariable('name1', 'type1', 'id1');
- workspace.createVariable('name2', 'type2', 'id2');
+
+ /**
+ * Assert that two nodes are equal.
+ * @param {!Element} actual the actual node.
+ * @param {!Element} expected the expected node.
+ */
+ function assertNodesEqual(actual, expected) {
+ var actualString = '\n' + Blockly.Xml.domToPrettyText(actual) + '\n';
+ var expectedString = '\n' + Blockly.Xml.domToPrettyText(expected) + '\n';
+
+ chai.assert.equal(actual.tagName, expected.tagName);
+ for (var i = 0, attr; (attr = expected.attributes[i]); i++) {
+ chai.assert.equal(actual.getAttribute(attr.name), attr.value,
+ `expected attribute ${attr.name} on ${actualString} to match ` +
+ `${expectedString}`);
+ }
+ chai.assert.equal(actual.childElementCount, expected.childElementCount,
+ `expected node ${actualString} to have the same children as node ` +
+ `${expectedString}`);
+ for (var i = 0; i < expected.childElementCount; i++) {
+ assertNodesEqual(actual.children[i], expected.children[i]);
+ }
}
- suite('createVariable', function() {
- test('Undo only', function() {
- createTwoVarsDifferentTypes(this.workspace);
-
- this.workspace.undo();
- assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
- chai.assert.isNull(this.workspace.getVariableById('id2'));
-
- this.workspace.undo();
- chai.assert.isNull(this.workspace.getVariableById('id1'));
- chai.assert.isNull(this.workspace.getVariableById('id2'));
- });
-
- test('Undo and redo', function() {
- createTwoVarsDifferentTypes(this.workspace);
-
- this.workspace.undo();
- assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
- chai.assert.isNull(this.workspace.getVariableById('id2'));
-
- this.workspace.undo(true);
-
- // Expect that variable 'id2' is recreated
- assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
- assertVariableValues(this.workspace, 'name2', 'type2', 'id2');
-
- this.workspace.undo();
- this.workspace.undo();
- chai.assert.isNull(this.workspace.getVariableById('id1'));
- chai.assert.isNull(this.workspace.getVariableById('id2'));
- this.workspace.undo(true);
-
- // Expect that variable 'id1' is recreated
- assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
- chai.assert.isNull(this.workspace.getVariableById('id2'));
- });
- });
-
- suite('deleteVariableById', function() {
- test('Undo only no usages', function() {
- createTwoVarsDifferentTypes(this.workspace);
- this.workspace.deleteVariableById('id1');
- this.workspace.deleteVariableById('id2');
-
- this.workspace.undo();
- chai.assert.isNull(this.workspace.getVariableById('id1'));
- assertVariableValues(this.workspace, 'name2', 'type2', 'id2');
-
- this.workspace.undo();
- assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
- assertVariableValues(this.workspace, 'name2', 'type2', 'id2');
- });
-
- test('Undo only with usages', function() {
- createTwoVarsDifferentTypes(this.workspace);
- // Create blocks to refer to both of them.
- createVarBlocksNoEvents(this.workspace, ['id1', 'id2']);
- this.workspace.deleteVariableById('id1');
- this.workspace.deleteVariableById('id2');
-
- this.workspace.undo();
- assertBlockVarModelName(this.workspace, 0, 'name2');
- chai.assert.isNull(this.workspace.getVariableById('id1'));
- assertVariableValues(this.workspace, 'name2', 'type2', 'id2');
-
- this.workspace.undo();
- assertBlockVarModelName(this.workspace, 0, 'name2');
- assertBlockVarModelName(this.workspace, 1, 'name1');
- assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
- assertVariableValues(this.workspace, 'name2', 'type2', 'id2');
- });
-
- test('Reference exists no usages', function() {
- createTwoVarsDifferentTypes(this.workspace);
- this.workspace.deleteVariableById('id1');
- this.workspace.deleteVariableById('id2');
-
- this.workspace.undo();
- chai.assert.isNull(this.workspace.getVariableById('id1'));
- assertVariableValues(this.workspace, 'name2', 'type2', 'id2');
-
- this.workspace.undo(true);
- // Expect that both variables are deleted
- chai.assert.isNull(this.workspace.getVariableById('id1'));
- chai.assert.isNull(this.workspace.getVariableById('id2'));
-
- this.workspace.undo();
- this.workspace.undo();
- assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
- assertVariableValues(this.workspace, 'name2', 'type2', 'id2');
-
- this.workspace.undo(true);
- // Expect that variable 'id2' is recreated
- chai.assert.isNull(this.workspace.getVariableById('id1'));
- assertVariableValues(this.workspace, 'name2', 'type2', 'id2');
- });
-
- test('Reference exists with usages', function() {
- createTwoVarsDifferentTypes(this.workspace);
- // Create blocks to refer to both of them.
- createVarBlocksNoEvents(this.workspace, ['id1', 'id2']);
- this.workspace.deleteVariableById('id1');
- this.workspace.deleteVariableById('id2');
-
- this.workspace.undo();
- assertBlockVarModelName(this.workspace, 0, 'name2');
- chai.assert.isNull(this.workspace.getVariableById('id1'));
- assertVariableValues(this.workspace, 'name2', 'type2', 'id2');
-
- this.workspace.undo(true);
- // Expect that both variables are deleted
- chai.assert.equal(this.workspace.topBlocks_.length, 0);
- chai.assert.isNull(this.workspace.getVariableById('id1'));
- chai.assert.isNull(this.workspace.getVariableById('id2'));
-
- this.workspace.undo();
- this.workspace.undo();
- assertBlockVarModelName(this.workspace, 0, 'name2');
- assertBlockVarModelName(this.workspace, 1, 'name1');
- assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
- assertVariableValues(this.workspace, 'name2', 'type2', 'id2');
-
- this.workspace.undo(true);
- // Expect that variable 'id2' is recreated
- assertBlockVarModelName(this.workspace,0, 'name2');
- chai.assert.isNull(this.workspace.getVariableById('id1'));
- assertVariableValues(this.workspace, 'name2', 'type2', 'id2');
- });
-
- test('Delete same variable twice no usages', function() {
- this.workspace.createVariable('name1', 'type1', 'id1');
- this.workspace.deleteVariableById('id1');
- var workspace = this.workspace;
- assertWarnings(() => {
- workspace.deleteVariableById('id1');
- }, [/Can't delete non-existent variable/]);
- // Check the undoStack only recorded one delete event.
- var undoStack = this.workspace.undoStack_;
- chai.assert.equal(undoStack[undoStack.length - 1].type, 'var_delete');
- chai.assert.notEqual(undoStack[undoStack.length - 2].type, 'var_delete');
-
- // Undo delete
- this.workspace.undo();
- assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
-
- // Redo delete
- this.workspace.undo(true);
- chai.assert.isNull(this.workspace.getVariableById('id1'));
-
- // Redo delete, nothing should happen
- this.workspace.undo(true);
- chai.assert.isNull(this.workspace.getVariableById('id1'));
- });
-
- test('Delete same variable twice with usages', function() {
- this.workspace.createVariable('name1', 'type1', 'id1');
- createVarBlocksNoEvents(this.workspace, ['id1']);
- this.workspace.deleteVariableById('id1');
- var workspace = this.workspace;
- assertWarnings(() => {
- workspace.deleteVariableById('id1');
- }, [/Can't delete non-existent variable/]);
- // Check the undoStack only recorded one delete event.
- var undoStack = this.workspace.undoStack_;
- chai.assert.equal(undoStack[undoStack.length - 1].type, 'var_delete');
- chai.assert.equal(undoStack[undoStack.length - 2].type, 'delete');
- chai.assert.notEqual(undoStack[undoStack.length - 3].type, 'var_delete');
-
- // Undo delete
- this.workspace.undo();
- assertBlockVarModelName(this.workspace, 0, 'name1');
- assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
-
- // Redo delete
- this.workspace.undo(true);
- chai.assert.equal(this.workspace.topBlocks_.length, 0);
- chai.assert.isNull(this.workspace.getVariableById('id1'));
-
- // Redo delete, nothing should happen
- this.workspace.undo(true);
- chai.assert.equal(this.workspace.topBlocks_.length, 0);
- chai.assert.isNull(this.workspace.getVariableById('id1'));
- });
- });
-
- suite('renameVariableById', function() {
+ suite('Undo Delete', function() {
setup(function() {
- this.workspace.createVariable('name1', 'type1', 'id1');
+ Blockly.defineBlocksWithJsonArray([
+ {
+ "type": "stack_block",
+ "message0": "",
+ "previousStatement": null,
+ "nextStatement": null
+ },
+ {
+ "type": "row_block",
+ "message0": "%1",
+ "args0": [
+ {
+ "type": "input_value",
+ "name": "INPUT"
+ }
+ ],
+ "output": null
+ },
+ {
+ "type": "statement_block",
+ "message0": "%1",
+ "args0": [
+ {
+ "type": "input_statement",
+ "name": "STATEMENT"
+ }
+ ],
+ "previousStatement": null,
+ "nextStatement": null
+ }]);
});
- test('Reference exists no usages rename to name2', function() {
- this.workspace.renameVariableById('id1', 'name2');
+ teardown(function() {
+ delete Blockly.Blocks['stack_block'];
+ delete Blockly.Blocks['row_block'];
+ delete Blockly.Blocks['statement_block'];
+ });
+ function testUndoDelete(xmlText) {
+ var xml = Blockly.Xml.textToDom(xmlText);
+ Blockly.Xml.domToBlock(xml, this.workspace);
+ this.workspace.getTopBlocks()[0].dispose(false);
this.workspace.undo();
- assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
-
- this.workspace.undo(true);
- assertVariableValues(this.workspace, 'name2', 'type1', 'id1');
+ var newXml = Blockly.Xml.workspaceToDom(this.workspace);
+ assertNodesEqual(newXml.firstChild, xml);
+ }
+ test('Stack', function() {
+ testUndoDelete.call(this, '');
});
- test('Reference exists with usages rename to name2', function() {
- createVarBlocksNoEvents(this.workspace, ['id1']);
- this.workspace.renameVariableById('id1', 'name2');
+ test('Row', function() {
+ testUndoDelete.call(this, '');
+ });
+ test('Statement', function() {
+ testUndoDelete.call(this, '');
+ });
+
+ test('Stack w/ child', function() {
+ testUndoDelete.call(this,
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ );
+ });
+
+ test('Row w/ child', function() {
+ testUndoDelete.call(this,
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ );
+ });
+
+ test('Statement w/ child', function() {
+ testUndoDelete.call(this,
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ );
+ });
+
+ test('Stack w/ shadow', function() {
+ testUndoDelete.call(this,
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ );
+ });
+
+ test('Row w/ shadow', function() {
+ testUndoDelete.call(this,
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ );
+ });
+
+ test('Statement w/ shadow', function() {
+ testUndoDelete.call(this,
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ''
+ );
+ });
+ });
+
+ suite('Undo Connect', function() {
+
+ setup(function() {
+ Blockly.defineBlocksWithJsonArray([
+ {
+ "type": "stack_block",
+ "message0": "",
+ "previousStatement": null,
+ "nextStatement": null
+ },
+ {
+ "type": "row_block",
+ "message0": "%1",
+ "args0": [
+ {
+ "type": "input_value",
+ "name": "INPUT"
+ }
+ ],
+ "output": null
+ },
+ {
+ "type": "statement_block",
+ "message0": "%1",
+ "args0": [
+ {
+ "type": "input_statement",
+ "name": "STATEMENT"
+ }
+ ],
+ "previousStatement": null,
+ "nextStatement": null
+ }]);
+ });
+
+ teardown(function() {
+ delete Blockly.Blocks['stack_block'];
+ delete Blockly.Blocks['row_block'];
+ delete Blockly.Blocks['statement_block'];
+ });
+
+ function testUndoConnect(xmlText, parentId, childId, func) {
+ var xml = Blockly.Xml.textToDom(xmlText);
+ Blockly.Xml.domToWorkspace(xml, this.workspace);
+
+ var parent = this.workspace.getBlockById(parentId);
+ var child = this.workspace.getBlockById(childId);
+ func.call(this, parent, child);
this.workspace.undo();
- assertBlockVarModelName(this.workspace, 0, 'name1');
- assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
- this.workspace.undo(true);
- assertBlockVarModelName(this.workspace, 0, 'name2');
- assertVariableValues(this.workspace, 'name2', 'type1', 'id1');
+ var newXml = Blockly.Xml.workspaceToDom(this.workspace);
+ assertNodesEqual(newXml, xml);
+ }
+
+ test('Stack', function() {
+ var xml =
+ '' +
+ ' ' +
+ ' ' +
+ '';
+
+ testUndoConnect.call(this, xml, 1, 2, (parent, child) => {
+ parent.nextConnection.connect(child.previousConnection);
+ });
});
- test('Reference exists different capitalization no usages rename to Name1', function() {
- this.workspace.renameVariableById('id1', 'Name1');
+ test('Row', function() {
+ var xml =
+ '' +
+ ' ' +
+ ' ' +
+ '';
+ testUndoConnect.call(this, xml, 1, 2, (parent, child) => {
+ parent.getInput('INPUT').connection.connect(child.outputConnection);
+ });
+ });
+
+ test('Statement', function() {
+ var xml =
+ '' +
+ ' ' +
+ ' ' +
+ '';
+
+ testUndoConnect.call(this, xml, 1, 2, (parent, child) => {
+ parent.getInput('STATEMENT').connection
+ .connect(child.previousConnection);
+ });
+ });
+
+ test('Stack w/ child', function() {
+ var xml =
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '';
+
+ testUndoConnect.call(this, xml, 1, 2, (parent, child) => {
+ parent.nextConnection.connect(child.previousConnection);
+ });
+ });
+
+ test('Row w/ child', function() {
+ var xml =
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '';
+
+ testUndoConnect.call(this, xml, 1, 2, (parent, child) => {
+ parent.getInput('INPUT').connection.connect(child.outputConnection);
+ });
+ });
+
+ test('Statement w/ child', function() {
+ var xml =
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '';
+
+ testUndoConnect.call(this, xml, 1, 2, (parent, child) => {
+ parent.getInput('STATEMENT').connection
+ .connect(child.previousConnection);
+ });
+ });
+
+ test('Stack w/ shadow', function() {
+ var xml =
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '';
+
+ testUndoConnect.call(this, xml, 1, 2, (parent, child) => {
+ parent.nextConnection.connect(child.previousConnection);
+ });
+ });
+
+ test('Row w/ shadow', function() {
+ var xml =
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '';
+
+ testUndoConnect.call(this, xml, 1, 2, (parent, child) => {
+ parent.getInput('INPUT').connection.connect(child.outputConnection);
+ });
+ });
+
+ test('Statement w/ shadow', function() {
+ var xml =
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '';
+
+ testUndoConnect.call(this, xml, 1, 2, (parent, child) => {
+ parent.getInput('STATEMENT').connection
+ .connect(child.previousConnection);
+ });
+ });
+ });
+
+ suite('Undo Disconnect', function() {
+
+ setup(function() {
+ Blockly.defineBlocksWithJsonArray([
+ {
+ "type": "stack_block",
+ "message0": "",
+ "previousStatement": null,
+ "nextStatement": null
+ },
+ {
+ "type": "row_block",
+ "message0": "%1",
+ "args0": [
+ {
+ "type": "input_value",
+ "name": "INPUT"
+ }
+ ],
+ "output": null
+ },
+ {
+ "type": "statement_block",
+ "message0": "%1",
+ "args0": [
+ {
+ "type": "input_statement",
+ "name": "STATEMENT"
+ }
+ ],
+ "previousStatement": null,
+ "nextStatement": null
+ }]);
+ });
+
+ teardown(function() {
+ delete Blockly.Blocks['stack_block'];
+ delete Blockly.Blocks['row_block'];
+ delete Blockly.Blocks['statement_block'];
+ });
+
+ function testUndoDisconnect(xmlText, childId) {
+ var xml = Blockly.Xml.textToDom(xmlText);
+ Blockly.Xml.domToWorkspace(xml, this.workspace);
+
+ var child = this.workspace.getBlockById(childId);
+ if (child.outputConnection) {
+ child.outputConnection.disconnect();
+ } else {
+ child.previousConnection.disconnect();
+ }
this.workspace.undo();
- assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
- this.workspace.undo(true);
- assertVariableValues(this.workspace, 'Name1', 'type1', 'id1');
+ var newXml = Blockly.Xml.workspaceToDom(this.workspace);
+ assertNodesEqual(newXml, xml);
+ }
+
+ test('Stack', function() {
+ var xml =
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '';
+ testUndoDisconnect.call(this, xml, 2);
});
- test('Reference exists different capitalization with usages rename to Name1', function() {
- createVarBlocksNoEvents(this.workspace, ['id1']);
- this.workspace.renameVariableById('id1', 'Name1');
-
- this.workspace.undo();
- assertBlockVarModelName(this.workspace, 0, 'name1');
- assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
-
- this.workspace.undo(true);
- assertBlockVarModelName(this.workspace, 0, 'Name1');
- assertVariableValues(this.workspace, 'Name1', 'type1', 'id1');
+ test('Row', function() {
+ var xml =
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '';
+ testUndoDisconnect.call(this, xml, 2);
});
- suite('Two variables rename overlap', function() {
- test('Same type no usages rename variable with id1 to name2', function() {
- this.workspace.createVariable('name2', 'type1', 'id2');
- this.workspace.renameVariableById('id1', 'name2');
+ test('Statement', function() {
+ var xml =
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '';
+ testUndoDisconnect.call(this, xml, 2);
+ });
+
+ test('Stack w/ child', function() {
+ var xml =
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '';
+ testUndoDisconnect.call(this, xml, 2);
+ });
+
+ test('Row w/ child', function() {
+ var xml =
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '';
+ testUndoDisconnect.call(this, xml, 2);
+ });
+
+ test('Statement w/ child', function() {
+ var xml =
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '';
+ testUndoDisconnect.call(this, xml, 2);
+ });
+
+ test('Stack w/ shadow', function() {
+ // TODO: For some reason on next connections shadows are
+ // serialized second.
+ var xml =
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '';
+ testUndoDisconnect.call(this, xml, 2);
+ chai.assert.equal(this.workspace.getAllBlocks().length, 2,
+ 'expected there to only be 2 blocks on the workspace ' +
+ '(check for shadows)');
+ });
+
+ test('Row w/ shadow', function() {
+ var xml =
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '';
+ testUndoDisconnect.call(this, xml, 2);
+ chai.assert.equal(this.workspace.getAllBlocks().length, 2,
+ 'expected there to only be 2 blocks on the workspace ' +
+ '(check for shadows)');
+ });
+
+ test('Statement w/ shadow', function() {
+ var xml =
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '';
+ testUndoDisconnect.call(this, xml, 2);
+ });
+ });
+
+ suite('Variables', function() {
+ function createTwoVarsDifferentTypes(workspace) {
+ workspace.createVariable('name1', 'type1', 'id1');
+ workspace.createVariable('name2', 'type2', 'id2');
+ }
+
+ suite('createVariable', function() {
+ test('Undo only', function() {
+ createTwoVarsDifferentTypes(this.workspace);
this.workspace.undo();
assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
- assertVariableValues(this.workspace, 'name2', 'type1', 'id2');
+ chai.assert.isNull(this.workspace.getVariableById('id2'));
- this.workspace.undo(true);
- assertVariableValues(this.workspace, 'name2', 'type1', 'id2');
+ this.workspace.undo();
chai.assert.isNull(this.workspace.getVariableById('id1'));
+ chai.assert.isNull(this.workspace.getVariableById('id2'));
});
- test('Same type with usages rename variable with id1 to name2', function() {
- this.workspace.createVariable('name2', 'type1', 'id2');
- createVarBlocksNoEvents(this.workspace, ['id1', 'id2']);
- this.workspace.renameVariableById('id1', 'name2');
+ test('Undo and redo', function() {
+ createTwoVarsDifferentTypes(this.workspace);
this.workspace.undo();
- assertBlockVarModelName(this.workspace, 0, 'name1');
- assertBlockVarModelName(this.workspace, 1, 'name2');
assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
- assertVariableValues(this.workspace, 'name2', 'type1', 'id2');
+ chai.assert.isNull(this.workspace.getVariableById('id2'));
this.workspace.undo(true);
- assertVariableValues(this.workspace, 'name2', 'type1', 'id2');
+
+ // Expect that variable 'id2' is recreated
+ assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
+ assertVariableValues(this.workspace, 'name2', 'type2', 'id2');
+
+ this.workspace.undo();
+ this.workspace.undo();
chai.assert.isNull(this.workspace.getVariableById('id1'));
- });
+ chai.assert.isNull(this.workspace.getVariableById('id2'));
+ this.workspace.undo(true);
- test('Same type different capitalization no usages rename variable with id1 to Name2', function() {
- this.workspace.createVariable('name2', 'type1', 'id2');
- this.workspace.renameVariableById('id1', 'Name2');
+ // Expect that variable 'id1' is recreated
+ assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
+ chai.assert.isNull(this.workspace.getVariableById('id2'));
+ });
+ });
+
+ suite('deleteVariableById', function() {
+ test('Undo only no usages', function() {
+ createTwoVarsDifferentTypes(this.workspace);
+ this.workspace.deleteVariableById('id1');
+ this.workspace.deleteVariableById('id2');
this.workspace.undo();
- assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
- assertVariableValues(this.workspace, 'name2', 'type1', 'id2');
-
- this.workspace.undo(true);
- assertVariableValues(this.workspace, 'Name2', 'type1', 'id2');
- chai.assert.isNull(this.workspace.getVariable('name1'));
- });
-
- test('Same type different capitalization with usages rename variable with id1 to Name2', function() {
- this.workspace.createVariable('name2', 'type1', 'id2');
- createVarBlocksNoEvents(this.workspace, ['id1', 'id2']);
- this.workspace.renameVariableById('id1', 'Name2');
-
- this.workspace.undo();
- assertBlockVarModelName(this.workspace, 0, 'name1');
- assertBlockVarModelName(this.workspace, 1, 'name2');
- assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
- assertVariableValues(this.workspace, 'name2', 'type1', 'id2');
-
- this.workspace.undo(true);
- assertVariableValues(this.workspace, 'Name2', 'type1', 'id2');
chai.assert.isNull(this.workspace.getVariableById('id1'));
- assertBlockVarModelName(this.workspace, 0, 'Name2');
- assertBlockVarModelName(this.workspace, 1, 'Name2');
- });
-
- test('Different type no usages rename variable with id1 to name2', function() {
- this.workspace.createVariable('name2', 'type2', 'id2');
- this.workspace.renameVariableById('id1', 'name2');
+ assertVariableValues(this.workspace, 'name2', 'type2', 'id2');
this.workspace.undo();
assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
assertVariableValues(this.workspace, 'name2', 'type2', 'id2');
-
- this.workspace.undo(true);
- assertVariableValues(this.workspace, 'name2', 'type1', 'id1');
- assertVariableValues(this.workspace, 'name2', 'type2', 'id2');
});
- test('Different type with usages rename variable with id1 to name2', function() {
- this.workspace.createVariable('name2', 'type2', 'id2');
+ test('Undo only with usages', function() {
+ createTwoVarsDifferentTypes(this.workspace);
+ // Create blocks to refer to both of them.
createVarBlocksNoEvents(this.workspace, ['id1', 'id2']);
- this.workspace.renameVariableById('id1', 'name2');
+ this.workspace.deleteVariableById('id1');
+ this.workspace.deleteVariableById('id2');
this.workspace.undo();
- assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
- assertVariableValues(this.workspace, 'name2', 'type2', 'id2');
- assertBlockVarModelName(this.workspace, 0, 'name1');
- assertBlockVarModelName(this.workspace, 1, 'name2');
-
- this.workspace.undo(true);
- assertVariableValues(this.workspace, 'name2', 'type1', 'id1');
- assertVariableValues(this.workspace, 'name2', 'type2', 'id2');
assertBlockVarModelName(this.workspace, 0, 'name2');
- assertBlockVarModelName(this.workspace, 1, 'name2');
+ chai.assert.isNull(this.workspace.getVariableById('id1'));
+ assertVariableValues(this.workspace, 'name2', 'type2', 'id2');
+
+ this.workspace.undo();
+ assertBlockVarModelName(this.workspace, 0, 'name2');
+ assertBlockVarModelName(this.workspace, 1, 'name1');
+ assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
+ assertVariableValues(this.workspace, 'name2', 'type2', 'id2');
});
- test('Different type different capitalization no usages rename variable with id1 to Name2', function() {
- this.workspace.createVariable('name2', 'type2', 'id2');
- this.workspace.renameVariableById('id1', 'Name2');
+ test('Reference exists no usages', function() {
+ createTwoVarsDifferentTypes(this.workspace);
+ this.workspace.deleteVariableById('id1');
+ this.workspace.deleteVariableById('id2');
+ this.workspace.undo();
+ chai.assert.isNull(this.workspace.getVariableById('id1'));
+ assertVariableValues(this.workspace, 'name2', 'type2', 'id2');
+
+ this.workspace.undo(true);
+ // Expect that both variables are deleted
+ chai.assert.isNull(this.workspace.getVariableById('id1'));
+ chai.assert.isNull(this.workspace.getVariableById('id2'));
+
+ this.workspace.undo();
this.workspace.undo();
assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
assertVariableValues(this.workspace, 'name2', 'type2', 'id2');
this.workspace.undo(true);
- assertVariableValues(this.workspace, 'Name2', 'type1', 'id1');
+ // Expect that variable 'id2' is recreated
+ chai.assert.isNull(this.workspace.getVariableById('id1'));
assertVariableValues(this.workspace, 'name2', 'type2', 'id2');
});
- test('Different type different capitalization with usages rename variable with id1 to Name2', function() {
- this.workspace.createVariable('name2', 'type2', 'id2');
+ test('Reference exists with usages', function() {
+ createTwoVarsDifferentTypes(this.workspace);
+ // Create blocks to refer to both of them.
createVarBlocksNoEvents(this.workspace, ['id1', 'id2']);
- this.workspace.renameVariableById('id1', 'Name2');
+ this.workspace.deleteVariableById('id1');
+ this.workspace.deleteVariableById('id2');
+
+ this.workspace.undo();
+ assertBlockVarModelName(this.workspace, 0, 'name2');
+ chai.assert.isNull(this.workspace.getVariableById('id1'));
+ assertVariableValues(this.workspace, 'name2', 'type2', 'id2');
+
+ this.workspace.undo(true);
+ // Expect that both variables are deleted
+ chai.assert.equal(this.workspace.topBlocks_.length, 0);
+ chai.assert.isNull(this.workspace.getVariableById('id1'));
+ chai.assert.isNull(this.workspace.getVariableById('id2'));
+
+ this.workspace.undo();
+ this.workspace.undo();
+ assertBlockVarModelName(this.workspace, 0, 'name2');
+ assertBlockVarModelName(this.workspace, 1, 'name1');
+ assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
+ assertVariableValues(this.workspace, 'name2', 'type2', 'id2');
+
+ this.workspace.undo(true);
+ // Expect that variable 'id2' is recreated
+ assertBlockVarModelName(this.workspace,0, 'name2');
+ chai.assert.isNull(this.workspace.getVariableById('id1'));
+ assertVariableValues(this.workspace, 'name2', 'type2', 'id2');
+ });
+
+ test('Delete same variable twice no usages', function() {
+ this.workspace.createVariable('name1', 'type1', 'id1');
+ this.workspace.deleteVariableById('id1');
+ var workspace = this.workspace;
+ var warnings = captureWarnings(function() {
+ workspace.deleteVariableById('id1');
+ });
+ chai.assert.equal(warnings.length, 1,
+ 'Expected 1 warning for second deleteVariableById call.');
+
+ // Check the undoStack only recorded one delete event.
+ var undoStack = this.workspace.undoStack_;
+ chai.assert.equal(undoStack[undoStack.length - 1].type, 'var_delete');
+ chai.assert.notEqual(undoStack[undoStack.length - 2].type, 'var_delete');
+
+ // Undo delete
+ this.workspace.undo();
+ assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
+
+ // Redo delete
+ this.workspace.undo(true);
+ chai.assert.isNull(this.workspace.getVariableById('id1'));
+
+ // Redo delete, nothing should happen
+ this.workspace.undo(true);
+ chai.assert.isNull(this.workspace.getVariableById('id1'));
+ });
+
+ test('Delete same variable twice with usages', function() {
+ this.workspace.createVariable('name1', 'type1', 'id1');
+ createVarBlocksNoEvents(this.workspace, ['id1']);
+ this.workspace.deleteVariableById('id1');
+ var workspace = this.workspace;
+ var warnings = captureWarnings(function() {
+ workspace.deleteVariableById('id1');
+ });
+ chai.assert.equal(warnings.length, 1,
+ 'Expected 1 warning for second deleteVariableById call.');
+
+ // Check the undoStack only recorded one delete event.
+ var undoStack = this.workspace.undoStack_;
+ chai.assert.equal(undoStack[undoStack.length - 1].type, 'var_delete');
+ chai.assert.equal(undoStack[undoStack.length - 2].type, 'delete');
+ chai.assert.notEqual(undoStack[undoStack.length - 3].type, 'var_delete');
+
+ // Undo delete
+ this.workspace.undo();
+ assertBlockVarModelName(this.workspace, 0, 'name1');
+ assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
+
+ // Redo delete
+ this.workspace.undo(true);
+ chai.assert.equal(this.workspace.topBlocks_.length, 0);
+ chai.assert.isNull(this.workspace.getVariableById('id1'));
+
+ // Redo delete, nothing should happen
+ this.workspace.undo(true);
+ chai.assert.equal(this.workspace.topBlocks_.length, 0);
+ chai.assert.isNull(this.workspace.getVariableById('id1'));
+ });
+ });
+
+ suite('renameVariableById', function() {
+ setup(function() {
+ this.workspace.createVariable('name1', 'type1', 'id1');
+ });
+
+ test('Reference exists no usages rename to name2', function() {
+ this.workspace.renameVariableById('id1', 'name2');
this.workspace.undo();
assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
- assertVariableValues(this.workspace, 'name2', 'type2', 'id2');
- assertBlockVarModelName(this.workspace, 0, 'name1');
- assertBlockVarModelName(this.workspace, 1, 'name2');
this.workspace.undo(true);
- assertVariableValues(this.workspace, 'Name2', 'type1', 'id1');
- assertVariableValues(this.workspace, 'name2', 'type2', 'id2');
- assertBlockVarModelName(this.workspace, 0, 'Name2');
- assertBlockVarModelName(this.workspace, 1, 'name2');
+ assertVariableValues(this.workspace, 'name2', 'type1', 'id1');
+
+ });
+
+ test('Reference exists with usages rename to name2', function() {
+ createVarBlocksNoEvents(this.workspace, ['id1']);
+ this.workspace.renameVariableById('id1', 'name2');
+
+ this.workspace.undo();
+ assertBlockVarModelName(this.workspace, 0, 'name1');
+ assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
+
+ this.workspace.undo(true);
+ assertBlockVarModelName(this.workspace, 0, 'name2');
+ assertVariableValues(this.workspace, 'name2', 'type1', 'id1');
+ });
+
+ test('Reference exists different capitalization no usages rename to Name1', function() {
+ this.workspace.renameVariableById('id1', 'Name1');
+
+ this.workspace.undo();
+ assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
+
+ this.workspace.undo(true);
+ assertVariableValues(this.workspace, 'Name1', 'type1', 'id1');
+ });
+
+ test('Reference exists different capitalization with usages rename to Name1', function() {
+ createVarBlocksNoEvents(this.workspace, ['id1']);
+ this.workspace.renameVariableById('id1', 'Name1');
+
+ this.workspace.undo();
+ assertBlockVarModelName(this.workspace, 0, 'name1');
+ assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
+
+ this.workspace.undo(true);
+ assertBlockVarModelName(this.workspace, 0, 'Name1');
+ assertVariableValues(this.workspace, 'Name1', 'type1', 'id1');
+ });
+
+ suite('Two variables rename overlap', function() {
+ test('Same type no usages rename variable with id1 to name2', function() {
+ this.workspace.createVariable('name2', 'type1', 'id2');
+ this.workspace.renameVariableById('id1', 'name2');
+
+ this.workspace.undo();
+ assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
+ assertVariableValues(this.workspace, 'name2', 'type1', 'id2');
+
+ this.workspace.undo(true);
+ assertVariableValues(this.workspace, 'name2', 'type1', 'id2');
+ chai.assert.isNull(this.workspace.getVariableById('id1'));
+ });
+
+ test('Same type with usages rename variable with id1 to name2', function() {
+ this.workspace.createVariable('name2', 'type1', 'id2');
+ createVarBlocksNoEvents(this.workspace, ['id1', 'id2']);
+ this.workspace.renameVariableById('id1', 'name2');
+
+ this.workspace.undo();
+ assertBlockVarModelName(this.workspace, 0, 'name1');
+ assertBlockVarModelName(this.workspace, 1, 'name2');
+ assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
+ assertVariableValues(this.workspace, 'name2', 'type1', 'id2');
+
+ this.workspace.undo(true);
+ assertVariableValues(this.workspace, 'name2', 'type1', 'id2');
+ chai.assert.isNull(this.workspace.getVariableById('id1'));
+ });
+
+ test('Same type different capitalization no usages rename variable with id1 to Name2', function() {
+ this.workspace.createVariable('name2', 'type1', 'id2');
+ this.workspace.renameVariableById('id1', 'Name2');
+
+ this.workspace.undo();
+ assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
+ assertVariableValues(this.workspace, 'name2', 'type1', 'id2');
+
+ this.workspace.undo(true);
+ assertVariableValues(this.workspace, 'Name2', 'type1', 'id2');
+ chai.assert.isNull(this.workspace.getVariable('name1'));
+ });
+
+ test('Same type different capitalization with usages rename variable with id1 to Name2', function() {
+ this.workspace.createVariable('name2', 'type1', 'id2');
+ createVarBlocksNoEvents(this.workspace, ['id1', 'id2']);
+ this.workspace.renameVariableById('id1', 'Name2');
+
+ this.workspace.undo();
+ assertBlockVarModelName(this.workspace, 0, 'name1');
+ assertBlockVarModelName(this.workspace, 1, 'name2');
+ assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
+ assertVariableValues(this.workspace, 'name2', 'type1', 'id2');
+
+ this.workspace.undo(true);
+ assertVariableValues(this.workspace, 'Name2', 'type1', 'id2');
+ chai.assert.isNull(this.workspace.getVariableById('id1'));
+ assertBlockVarModelName(this.workspace, 0, 'Name2');
+ assertBlockVarModelName(this.workspace, 1, 'Name2');
+ });
+
+ test('Different type no usages rename variable with id1 to name2', function() {
+ this.workspace.createVariable('name2', 'type2', 'id2');
+ this.workspace.renameVariableById('id1', 'name2');
+
+ this.workspace.undo();
+ assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
+ assertVariableValues(this.workspace, 'name2', 'type2', 'id2');
+
+ this.workspace.undo(true);
+ assertVariableValues(this.workspace, 'name2', 'type1', 'id1');
+ assertVariableValues(this.workspace, 'name2', 'type2', 'id2');
+ });
+
+ test('Different type with usages rename variable with id1 to name2', function() {
+ this.workspace.createVariable('name2', 'type2', 'id2');
+ createVarBlocksNoEvents(this.workspace, ['id1', 'id2']);
+ this.workspace.renameVariableById('id1', 'name2');
+
+ this.workspace.undo();
+ assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
+ assertVariableValues(this.workspace, 'name2', 'type2', 'id2');
+ assertBlockVarModelName(this.workspace, 0, 'name1');
+ assertBlockVarModelName(this.workspace, 1, 'name2');
+
+ this.workspace.undo(true);
+ assertVariableValues(this.workspace, 'name2', 'type1', 'id1');
+ assertVariableValues(this.workspace, 'name2', 'type2', 'id2');
+ assertBlockVarModelName(this.workspace, 0, 'name2');
+ assertBlockVarModelName(this.workspace, 1, 'name2');
+ });
+
+ test('Different type different capitalization no usages rename variable with id1 to Name2', function() {
+ this.workspace.createVariable('name2', 'type2', 'id2');
+ this.workspace.renameVariableById('id1', 'Name2');
+
+ this.workspace.undo();
+ assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
+ assertVariableValues(this.workspace, 'name2', 'type2', 'id2');
+
+ this.workspace.undo(true);
+ assertVariableValues(this.workspace, 'Name2', 'type1', 'id1');
+ assertVariableValues(this.workspace, 'name2', 'type2', 'id2');
+ });
+
+ test('Different type different capitalization with usages rename variable with id1 to Name2', function() {
+ this.workspace.createVariable('name2', 'type2', 'id2');
+ createVarBlocksNoEvents(this.workspace, ['id1', 'id2']);
+ this.workspace.renameVariableById('id1', 'Name2');
+
+ this.workspace.undo();
+ assertVariableValues(this.workspace, 'name1', 'type1', 'id1');
+ assertVariableValues(this.workspace, 'name2', 'type2', 'id2');
+ assertBlockVarModelName(this.workspace, 0, 'name1');
+ assertBlockVarModelName(this.workspace, 1, 'name2');
+
+ this.workspace.undo(true);
+ assertVariableValues(this.workspace, 'Name2', 'type1', 'id1');
+ assertVariableValues(this.workspace, 'name2', 'type2', 'id2');
+ assertBlockVarModelName(this.workspace, 0, 'Name2');
+ assertBlockVarModelName(this.workspace, 1, 'name2');
+ });
});
});
});