diff --git a/core/connection.js b/core/connection.js index e0b24d7d4..9e769a1a9 100644 --- a/core/connection.js +++ b/core/connection.js @@ -250,29 +250,7 @@ Blockly.Connection.prototype.isConnected = function() { * an error code otherwise. */ Blockly.Connection.prototype.canConnectWithReason = function(target) { - return this.sourceBlock_.workspace.connectionTypeChecker.canConnectWithReason(this, target); - // if (!target) { - // return Blockly.Connection.REASON_TARGET_NULL; - // } - // if (this.isSuperior()) { - // var blockA = this.sourceBlock_; - // var blockB = target.getSourceBlock(); - // } else { - // var blockB = this.sourceBlock_; - // var blockA = target.getSourceBlock(); - // } - // if (blockA && blockA == blockB) { - // return Blockly.Connection.REASON_SELF_CONNECTION; - // } else if (target.type != Blockly.OPPOSITE_TYPE[this.type]) { - // return Blockly.Connection.REASON_WRONG_TYPE; - // } else if (blockA && blockB && blockA.workspace !== blockB.workspace) { - // return Blockly.Connection.REASON_DIFFERENT_WORKSPACES; - // } else if (!this.checkType(target)) { - // return Blockly.Connection.REASON_CHECKS_FAILED; - // } else if (blockA.isShadow() && !blockB.isShadow()) { - // return Blockly.Connection.REASON_SHADOW_PARENT; - // } - // return Blockly.Connection.CAN_CONNECT; + return this.getConnectionTypeChecker().canConnectWithReason(this, target); }; /** @@ -284,57 +262,60 @@ Blockly.Connection.prototype.canConnectWithReason = function(target) { */ Blockly.Connection.prototype.checkConnection = function(target) { - var checker = this.sourceBlock_.workspace.connectionTypeChecker; + var checker = this.getConnectionTypeChecker(); var reason = checker.canConnectWithReason(this, target); if (reason != Blockly.Connection.CAN_CONNECT) { throw Error(checker.getErrorMessage(reason, this, target)); } }; -/** - * Check if the two connections can be dragged to connect to each other. - * This is used by the connection database when searching for the closest - * connection. - * @param {!Blockly.Connection} candidate A nearby connection to check, which - * must be a previous connection. - * @return {boolean} True if the connection is allowed, false otherwise. - * @private - */ -Blockly.Connection.prototype.canConnectToPrevious_ = function(candidate) { - if (this.targetConnection) { - // This connection is already occupied. - // A next connection will never disconnect itself mid-drag. - return false; - } - - // Don't let blocks try to connect to themselves or ones they nest. - if (Blockly.draggingConnections.indexOf(candidate) != -1) { - return false; - } - - if (!candidate.targetConnection) { - return true; - } - - var targetBlock = candidate.targetBlock(); - // If it is connected to a real block, game over. - if (!targetBlock.isInsertionMarker()) { - return false; - } - // If it's connected to an insertion marker but that insertion marker - // is the first block in a stack, it's still fine. If that insertion - // marker is in the middle of a stack, it won't work. - return !targetBlock.getPreviousBlock(); +Blockly.Connection.prototype.getConnectionTypeChecker = function() { + return this.sourceBlock_.workspace.connectionTypeChecker; }; +// /** +// * Check if the two connections can be dragged to connect to each other. +// * This is used by the connection database when searching for the closest +// * connection. +// * @param {!Blockly.Connection} candidate A nearby connection to check, which +// * must be a previous connection. +// * @return {boolean} True if the connection is allowed, false otherwise. +// * @private +// */ +// Blockly.Connection.prototype.canConnectToPrevious_ = function(candidate) { +// if (this.targetConnection) { +// // This connection is already occupied. +// // A next connection will never disconnect itself mid-drag. +// return false; +// } + +// // Don't let blocks try to connect to themselves or ones they nest. +// if (Blockly.draggingConnections.indexOf(candidate) != -1) { +// return false; +// } + +// if (!candidate.targetConnection) { +// return true; +// } + +// var targetBlock = candidate.targetBlock(); +// // If it is connected to a real block, game over. +// if (!targetBlock.isInsertionMarker()) { +// return false; +// } +// // If it's connected to an insertion marker but that insertion marker +// // is the first block in a stack, it's still fine. If that insertion +// // marker is in the middle of a stack, it won't work. +// return !targetBlock.getPreviousBlock(); +// }; + /** * Check if the two connections can be dragged to connect to each other. * @param {!Blockly.Connection} candidate A nearby connection to check. * @return {boolean} True if the connection is allowed, false otherwise. */ Blockly.Connection.prototype.isConnectionAllowed = function(candidate) { - var checker = this.sourceBlock_.workspace.connectionTypeChecker; - return checker.isConnectionAllowed(this, candidate); + return this.getConnectionTypeChecker().isConnectionAllowed(this, candidate); }; /** @@ -357,7 +338,7 @@ Blockly.Connection.prototype.connect = function(otherConnection) { return; } - var checker = this.sourceBlock_.workspace.connectionTypeChecker; + var checker = this.getConnectionTypeChecker(); var reason = checker.canConnectWithReason(this, otherConnection); if (reason != Blockly.Connection.CAN_CONNECT) { throw Error(checker.getErrorMessage(reason, this, otherConnection)); @@ -538,8 +519,7 @@ Blockly.Connection.prototype.targetBlock = function() { * @return {boolean} True if the connections share a type. */ Blockly.Connection.prototype.checkType = function(otherConnection) { - return this.sourceBlock_.workspace.connectionTypeChecker.checkType( - this, otherConnection); + return this.getConnectionTypeChecker().checkType(this, otherConnection); }; /** diff --git a/core/connection_checks.js b/core/connection_checks.js index 03da43ccd..ade3ef949 100644 --- a/core/connection_checks.js +++ b/core/connection_checks.js @@ -53,47 +53,32 @@ Blockly.ConnectionTypeChecker.prototype.getErrorMessage = function(errorCode, * an error code otherwise. */ Blockly.ConnectionTypeChecker.prototype.canConnectWithReason = function(one, two) { - if (!two) { + if (!one || !two) { return Blockly.Connection.REASON_TARGET_NULL; } if (one.isSuperior()) { - var blockA = one.sourceBlock_; + var blockA = one.getSourceBlock(); var blockB = two.getSourceBlock(); } else { - var blockB = one.sourceBlock_; + var blockB = one.getSourceBlock(); var blockA = two.getSourceBlock(); } - + // TODO (fenichel): The null checks seem like they're only for making tests + // work better. if (blockA && blockA == blockB) { return Blockly.Connection.REASON_SELF_CONNECTION; } else if (two.type != Blockly.OPPOSITE_TYPE[one.type]) { return Blockly.Connection.REASON_WRONG_TYPE; } else if (blockA && blockB && blockA.workspace !== blockB.workspace) { return Blockly.Connection.REASON_DIFFERENT_WORKSPACES; - } else if (!this.checkType(one, two)) { - return Blockly.Connection.REASON_CHECKS_FAILED; } else if (blockA.isShadow() && !blockB.isShadow()) { return Blockly.Connection.REASON_SHADOW_PARENT; + } else if (!this.checkType(one, two)) { + return Blockly.Connection.REASON_CHECKS_FAILED; } return Blockly.Connection.CAN_CONNECT; }; -/** - * Checks whether the current connection and target connection are compatible - * and throws an exception if they are not. - * @param {!Blockly.Connection} one The connection to check compatibility - * with. - * @param {!Blockly.Connection} two The connection to check compatibility - * with. - * @package - */ -// Blockly.ConnectionTypeChecker.prototype.checkConnection = function(one, two) { -// var reason = one.canConnectWithReason(two); -// if (reason != Blockly.Connection.CAN_CONNECT) { -// throw Error(this.getErrorMessage_(reason, one, two)); -// } -// }; - /** * Is this connection compatible with another connection with respect to the * value type system. E.g. square_root("Hello") is not compatible. diff --git a/tests/mocha/connection_test.js b/tests/mocha/connection_test.js index f78584cfa..a4ac148d7 100644 --- a/tests/mocha/connection_test.js +++ b/tests/mocha/connection_test.js @@ -4,11 +4,23 @@ * SPDX-License-Identifier: Apache-2.0 */ -suite('Connections', function() { +suite('Connection type checker', function() { + suiteSetup(function() { + this.checker = new Blockly.ConnectionTypeChecker(); + }); suite('Can Connect With Reason', function() { + function assertReasonHelper(checker, one, two, reason) { + chai.assert.equal(checker.canConnectWithReason(one, two), reason); + // Order should not matter. + chai.assert.equal(checker.canConnectWithReason(two, one), reason); + } + test('Target Null', function() { var connection = new Blockly.Connection({}, Blockly.INPUT_VALUE); - chai.assert.equal(connection.canConnectWithReason(null), + assertReasonHelper( + this.checker, + connection, + null, Blockly.Connection.REASON_TARGET_NULL); }); test('Target Self', function() { @@ -16,7 +28,10 @@ suite('Connections', function() { var connection1 = new Blockly.Connection(block, Blockly.INPUT_VALUE); var connection2 = new Blockly.Connection(block, Blockly.OUTPUT_VALUE); - chai.assert.equal(connection1.canConnectWithReason(connection2), + assertReasonHelper( + this.checker, + connection1, + connection2, Blockly.Connection.REASON_SELF_CONNECTION); }); test('Different Workspaces', function() { @@ -25,7 +40,10 @@ suite('Connections', function() { var connection2 = new Blockly.Connection( {workspace: 2}, Blockly.OUTPUT_VALUE); - chai.assert.equal(connection1.canConnectWithReason(connection2), + assertReasonHelper( + this.checker, + connection1, + connection2, Blockly.Connection.REASON_DIFFERENT_WORKSPACES); }); suite('Types', function() { @@ -46,51 +64,87 @@ suite('Connections', function() { inBlock, Blockly.INPUT_VALUE); }); test('Previous, Next', function() { - chai.assert.equal(this.previous.canConnectWithReason(this.next), + assertReasonHelper( + this.checker, + this.previous, + this.next, Blockly.Connection.CAN_CONNECT); }); test('Previous, Output', function() { - chai.assert.equal(this.previous.canConnectWithReason(this.output), + assertReasonHelper( + this.checker, + this.previous, + this.output, Blockly.Connection.REASON_WRONG_TYPE); }); test('Previous, Input', function() { - chai.assert.equal(this.previous.canConnectWithReason(this.input), + assertReasonHelper( + this.checker, + this.previous, + this.input, Blockly.Connection.REASON_WRONG_TYPE); }); test('Next, Previous', function() { - chai.assert.equal(this.next.canConnectWithReason(this.previous), + assertReasonHelper( + this.checker, + this.next, + this.previous, Blockly.Connection.CAN_CONNECT); }); test('Next, Output', function() { - chai.assert.equal(this.next.canConnectWithReason(this.output), + assertReasonHelper( + this.checker, + this.next, + this.output, Blockly.Connection.REASON_WRONG_TYPE); }); test('Next, Input', function() { - chai.assert.equal(this.next.canConnectWithReason(this.input), + assertReasonHelper( + this.checker, + this.next, + this.input, Blockly.Connection.REASON_WRONG_TYPE); }); test('Output, Previous', function() { - chai.assert.equal(this.output.canConnectWithReason(this.previous), + assertReasonHelper( + this.checker, + this.previous, + this.output, Blockly.Connection.REASON_WRONG_TYPE); }); test('Output, Next', function() { - chai.assert.equal(this.output.canConnectWithReason(this.next), + assertReasonHelper( + this.checker, + this.output, + this.next, Blockly.Connection.REASON_WRONG_TYPE); }); test('Output, Input', function() { - chai.assert.equal(this.output.canConnectWithReason(this.input), + assertReasonHelper( + this.checker, + this.output, + this.input, Blockly.Connection.CAN_CONNECT); }); test('Input, Previous', function() { - chai.assert.equal(this.input.canConnectWithReason(this.previous), + assertReasonHelper( + this.checker, + this.previous, + this.input, Blockly.Connection.REASON_WRONG_TYPE); }); test('Input, Next', function() { - chai.assert.equal(this.input.canConnectWithReason(this.next), + assertReasonHelper( + this.checker, + this.input, + this.next, Blockly.Connection.REASON_WRONG_TYPE); }); test('Input, Output', function() { - chai.assert.equal(this.input.canConnectWithReason(this.output), + assertReasonHelper( + this.checker, + this.input, + this.output, Blockly.Connection.CAN_CONNECT); }); }); @@ -101,7 +155,10 @@ suite('Connections', function() { var prev = new Blockly.Connection(prevBlock, Blockly.PREVIOUS_STATEMENT); var next = new Blockly.Connection(nextBlock, Blockly.NEXT_STATEMENT); - chai.assert.equal(prev.canConnectWithReason(next), + assertReasonHelper( + this.checker, + prev, + next, Blockly.Connection.CAN_CONNECT); }); test('Next Shadow', function() { @@ -110,7 +167,10 @@ suite('Connections', function() { var prev = new Blockly.Connection(prevBlock, Blockly.PREVIOUS_STATEMENT); var next = new Blockly.Connection(nextBlock, Blockly.NEXT_STATEMENT); - chai.assert.equal(prev.canConnectWithReason(next), + assertReasonHelper( + this.checker, + prev, + next, Blockly.Connection.REASON_SHADOW_PARENT); }); test('Prev and Next Shadow', function() { @@ -119,7 +179,10 @@ suite('Connections', function() { var prev = new Blockly.Connection(prevBlock, Blockly.PREVIOUS_STATEMENT); var next = new Blockly.Connection(nextBlock, Blockly.NEXT_STATEMENT); - chai.assert.equal(prev.canConnectWithReason(next), + assertReasonHelper( + this.checker, + prev, + next, Blockly.Connection.CAN_CONNECT); }); test('Output Shadow', function() { @@ -128,7 +191,10 @@ suite('Connections', function() { var outCon = new Blockly.Connection(outBlock, Blockly.OUTPUT_VALUE); var inCon = new Blockly.Connection(inBlock, Blockly.INPUT_VALUE); - chai.assert.equal(outCon.canConnectWithReason(inCon), + assertReasonHelper( + this.checker, + outCon, + inCon, Blockly.Connection.CAN_CONNECT); }); test('Input Shadow', function() { @@ -137,7 +203,10 @@ suite('Connections', function() { var outCon = new Blockly.Connection(outBlock, Blockly.OUTPUT_VALUE); var inCon = new Blockly.Connection(inBlock, Blockly.INPUT_VALUE); - chai.assert.equal(outCon.canConnectWithReason(inCon), + assertReasonHelper( + this.checker, + outCon, + inCon, Blockly.Connection.REASON_SHADOW_PARENT); }); test('Output and Input Shadow', function() { @@ -146,7 +215,10 @@ suite('Connections', function() { var outCon = new Blockly.Connection(outBlock, Blockly.OUTPUT_VALUE); var inCon = new Blockly.Connection(inBlock, Blockly.INPUT_VALUE); - chai.assert.equal(outCon.canConnectWithReason(inCon), + assertReasonHelper( + this.checker, + outCon, + inCon, Blockly.Connection.CAN_CONNECT); }); }); @@ -156,32 +228,38 @@ suite('Connections', function() { this.con1 = new Blockly.Connection({}, Blockly.PREVIOUS_STATEMENT); this.con2 = new Blockly.Connection({}, Blockly.NEXT_STATEMENT); }); + function assertCheckTypes(checker, one, two) { + chai.assert.isTrue(checker.checkType(one, two)); + // Order should not matter. + chai.assert.isTrue(checker.checkType(one, two)); + } test('No Types', function() { - chai.assert.isTrue(this.con1.checkType((this.con2))); + assertCheckTypes(this.checker, this.con1, this.con2); + chai.assert.isTrue(this.checker.checkType(this.con1, this.con2)); }); test('Same Type', function() { this.con1.setCheck('type1'); this.con2.setCheck('type1'); - chai.assert.isTrue(this.con1.checkType((this.con2))); + assertCheckTypes(this.checker, this.con1, this.con2); }); test('Same Types', function() { this.con1.setCheck(['type1', 'type2']); this.con2.setCheck(['type1', 'type2']); - chai.assert.isTrue(this.con1.checkType((this.con2))); + assertCheckTypes(this.checker, this.con1, this.con2); }); test('Single Same Type', function() { this.con1.setCheck(['type1', 'type2']); this.con2.setCheck(['type1', 'type3']); - chai.assert.isTrue(this.con1.checkType((this.con2))); + assertCheckTypes(this.checker, this.con1, this.con2); }); test('One Typed, One Promiscuous', function() { this.con1.setCheck('type1'); - chai.assert.isTrue(this.con1.checkType((this.con2))); + assertCheckTypes(this.checker, this.con1, this.con2); }); test('No Compatible Types', function() { this.con1.setCheck('type1'); this.con2.setCheck('type2'); - chai.assert.isFalse(this.con1.checkType((this.con2))); + chai.assert.isFalse(this.checker.checkType(this.con1, this.con2)); }); }); });