/** * @license * Copyright 2019 Google LLC * SPDX-License-Identifier: Apache-2.0 */ goog.module('Blockly.test.connection'); const {createGenUidStubWithReturns, sharedTestSetup, sharedTestTeardown, workspaceTeardown} = goog.require('Blockly.test.helpers.setupTeardown'); const {defineRowBlock, defineStatementBlock, defineStackBlock} = goog.require('Blockly.test.helpers.blockDefinitions'); suite('Connection', function() { setup(function() { sharedTestSetup.call(this); this.workspace = sinon.createStubInstance(Blockly.Workspace); this.workspace.connectionChecker = new Blockly.ConnectionChecker(); this.createConnection = function(type) { const block = { workspace: this.workspace, isShadow: function() {return false;}, }; const connection = new Blockly.Connection(block, type); return connection; }; }); teardown(function() { sharedTestTeardown.call(this); }); suite('Set Shadow', 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) { const 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) { const 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) { const block = parent.getInputTargetBlock(inputName); chai.assert.notExists(block, `expected block ${block && block.id} to not be attached to ${inputName}`); } function assertNextNotHasBlock(parent) { const block = parent.getNextBlock(); chai.assert.notExists(block, `expected block ${block && block.id} to not be attached to next connection`); } function assertSerialization(block, jso, xmlText) { const actualJso = Blockly.serialization.blocks .save(block, {addNextBlocks: true}); const actualXml = Blockly.Xml.domToText(Blockly.Xml.blockToDom(block)); chai.assert.deepEqual(actualJso, jso); chai.assert.equal(actualXml, xmlText); } const 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(); defineRowBlock(); defineStatementBlock(); defineStackBlock(); createGenUidStubWithReturns( new Array(30).fill().map((_, i) => 'id' + i)); }); teardown(function() { workspaceTeardown.call(this, this.workspace); }); suite('setShadowDom', function() { suite('Add - No Block Connected', function() { // These are defined separately in each suite. function createRowBlock(workspace) { const block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom( '' ), workspace); return block; } function createStatementBlock(workspace) { const block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom( '' ), workspace); return block; } function createStackBlock(workspace) { const block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom( '' ), workspace); return block; } test('Value', function() { const parent = createRowBlock(this.workspace); const xml = Blockly.Xml.textToDom( '' ); parent.getInput('INPUT').connection.setShadowDom(xml); assertInputHasBlock(parent, 'INPUT', true); assertSerialization( parent, { 'type': 'row_block', 'id': 'id0', 'inputs': { 'INPUT': { 'shadow': { 'type': 'row_block', 'id': 'id1', }, }, }, }, '' + '' + '' + '' + '' ); }); test('Multiple Value', function() { const parent = createRowBlock(this.workspace); const xml = Blockly.Xml.textToDom( '' + ' ' + ' ' + ' ' + '' ); parent.getInput('INPUT').connection.setShadowDom(xml); assertInputHasBlock(parent, 'INPUT', true); assertInputHasBlock( parent.getInputTargetBlock('INPUT'), 'INPUT', true); assertSerialization( parent, { 'type': 'row_block', 'id': 'id0', 'inputs': { 'INPUT': { 'shadow': { 'type': 'row_block', 'id': 'id1', 'inputs': { 'INPUT': { 'shadow': { 'type': 'row_block', 'id': 'id2', }, }, }, }, }, }, }, '' + '' + '' + '' + '' + '' + '' + '' + '' ); }); test('Statement', function() { const parent = createStatementBlock(this.workspace); const xml = Blockly.Xml.textToDom( '' ); parent.getInput('NAME').connection.setShadowDom(xml); assertInputHasBlock(parent, 'NAME', true); assertSerialization( parent, { 'type': 'statement_block', 'id': 'id0', 'inputs': { 'NAME': { 'shadow': { 'type': 'statement_block', 'id': 'id1', }, }, }, }, '' + '' + '' + '' + '' ); }); test('Multiple Statement', function() { const parent = createStatementBlock(this.workspace); const xml = Blockly.Xml.textToDom( '' + ' ' + ' ' + ' ' + '' ); parent.getInput('NAME').connection.setShadowDom(xml); assertInputHasBlock(parent, 'NAME', true); assertInputHasBlock( parent.getInputTargetBlock('NAME'), 'NAME', true); assertSerialization( parent, { 'type': 'statement_block', 'id': 'id0', 'inputs': { 'NAME': { 'shadow': { 'type': 'statement_block', 'id': 'id1', 'inputs': { 'NAME': { 'shadow': { 'type': 'statement_block', 'id': 'id2', }, }, }, }, }, }, }, '' + '' + '' + '' + '' + '' + '' + '' + '' ); }); test('Next', function() { const parent = createStackBlock(this.workspace); const xml = Blockly.Xml.textToDom( '' ); parent.nextConnection.setShadowDom(xml); assertNextHasBlock(parent, true); assertSerialization( parent, { 'type': 'stack_block', 'id': 'id0', 'next': { 'shadow': { 'type': 'stack_block', 'id': 'id1', }, }, }, '' + '' + '' + '' + '' ); }); test('Multiple Next', function() { const parent = createStackBlock(this.workspace); const xml = Blockly.Xml.textToDom( '' + ' ' + ' ' + ' ' + '' ); parent.nextConnection.setShadowDom(xml); assertNextHasBlock(parent, true); assertNextHasBlock(parent.getNextBlock(), true); assertSerialization( parent, { 'type': 'stack_block', 'id': 'id0', 'next': { 'shadow': { 'type': 'stack_block', 'id': 'id1', 'next': { 'shadow': { 'type': 'stack_block', 'id': 'id2', }, }, }, }, }, '' + '' + '' + '' + '' + '' + '' + '' + '' ); }); }); suite('Add - With Block Connected', function() { // These are defined separately in each suite. function createRowBlocks(workspace) { const block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom( '' + ' ' + ' ' + ' ' + '' ), workspace); return block; } function createStatementBlocks(workspace) { const block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom( '' + ' ' + ' ' + ' ' + '' ), workspace); return block; } function createStackBlocks(workspace) { const block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom( '' + ' ' + ' ' + ' ' + '' ), workspace); return block; } test('Value', function() { const parent = createRowBlocks(this.workspace); const xml = Blockly.Xml.textToDom( '' ); parent.getInput('INPUT').connection.setShadowDom(xml); assertInputHasBlock(parent, 'INPUT', false); parent.getInput('INPUT').connection.disconnect(); assertInputHasBlock(parent, 'INPUT', true); assertSerialization( parent, { 'type': 'row_block', 'id': 'id0', 'inputs': { 'INPUT': { 'shadow': { 'type': 'row_block', 'id': 'id1', }, }, }, }, '' + '' + '' + '' + '' ); }); test('Multiple Value', function() { const parent = createRowBlocks(this.workspace); const 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); assertSerialization( parent, { 'type': 'row_block', 'id': 'id0', 'inputs': { 'INPUT': { 'shadow': { 'type': 'row_block', 'id': 'id1', 'inputs': { 'INPUT': { 'shadow': { 'type': 'row_block', 'id': 'id2', }, }, }, }, }, }, }, '' + '' + '' + '' + '' + '' + '' + '' + '' ); }); test('Statement', function() { const parent = createStatementBlocks(this.workspace); const xml = Blockly.Xml.textToDom( '' ); parent.getInput('NAME').connection.setShadowDom(xml); assertInputHasBlock(parent, 'NAME', false); parent.getInput('NAME').connection.disconnect(); assertInputHasBlock(parent, 'NAME', true); assertSerialization( parent, { 'type': 'statement_block', 'id': 'id0', 'inputs': { 'NAME': { 'shadow': { 'type': 'statement_block', 'id': 'id1', }, }, }, }, '' + '' + '' + '' + '' ); }); test('Multiple Statement', function() { const parent = createStatementBlocks(this.workspace); const xml = Blockly.Xml.textToDom( '' + ' ' + ' ' + ' ' + '' ); parent.getInput('NAME').connection.setShadowDom(xml); assertInputHasBlock(parent, 'NAME', false); assertInputNotHasBlock( parent.getInputTargetBlock('NAME'), 'NAME'); parent.getInput('NAME').connection.disconnect(); assertInputHasBlock(parent, 'NAME', true); assertInputHasBlock( parent.getInputTargetBlock('NAME'), 'NAME', true); assertSerialization( parent, { 'type': 'statement_block', 'id': 'id0', 'inputs': { 'NAME': { 'shadow': { 'type': 'statement_block', 'id': 'id1', 'inputs': { 'NAME': { 'shadow': { 'type': 'statement_block', 'id': 'id2', }, }, }, }, }, }, }, '' + '' + '' + '' + '' + '' + '' + '' + '' ); }); test('Next', function() { const parent = createStackBlocks(this.workspace); const xml = Blockly.Xml.textToDom( '' ); parent.nextConnection.setShadowDom(xml); assertNextHasBlock(parent, false); parent.nextConnection.disconnect(); assertNextHasBlock(parent, true); assertSerialization( parent, { 'type': 'stack_block', 'id': 'id0', 'next': { 'shadow': { 'type': 'stack_block', 'id': 'id1', }, }, }, '' + '' + '' + '' + '' ); }); test('Multiple Next', function() { const parent = createStackBlocks(this.workspace); const xml = Blockly.Xml.textToDom( '' + ' ' + ' ' + ' ' + '' ); parent.nextConnection.setShadowDom(xml); assertNextHasBlock(parent, false); assertNextNotHasBlock(parent.getNextBlock()); parent.nextConnection.disconnect(); assertNextHasBlock(parent, true); assertNextHasBlock(parent.getNextBlock(), true); assertSerialization( parent, { 'type': 'stack_block', 'id': 'id0', 'next': { 'shadow': { 'type': 'stack_block', 'id': 'id1', 'next': { 'shadow': { 'type': 'stack_block', 'id': 'id2', }, }, }, }, }, '' + '' + '' + '' + '' + '' + '' + '' + '' ); }); }); suite('Add - With Shadow Connected', function() { // These are defined separately in each suite. function createRowBlock(workspace) { const block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom( '' ), workspace); return block; } function createStatementBlock(workspace) { const block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom( '' ), workspace); return block; } function createStackBlock(workspace) { const block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom( '' ), workspace); return block; } test('Value', function() { const parent = createRowBlock(this.workspace); const xml1 = Blockly.Xml.textToDom( '' ); parent.getInput('INPUT').connection.setShadowDom(xml1); assertInputHasBlock(parent, 'INPUT', true, '1'); const xml2 = Blockly.Xml.textToDom(''); parent.getInput('INPUT').connection.setShadowDom(xml2); assertInputHasBlock(parent, 'INPUT', true, '2'); assertSerialization( parent, { 'type': 'row_block', 'id': 'id0', 'inputs': { 'INPUT': { 'shadow': { 'type': 'row_block', 'id': '2', }, }, }, }, '' + '' + '' + '' + '' ); }); test('Multiple Value', function() { const parent = createRowBlock(this.workspace); const xml1 = Blockly.Xml.textToDom( '' + ' ' + ' ' + ' ' + ''); parent.getInput('INPUT').connection.setShadowDom(xml1); assertInputHasBlock(parent, 'INPUT', true, '1'); assertInputHasBlock( parent.getInputTargetBlock('INPUT'), 'INPUT', true, 'a'); const xml2 = Blockly.Xml.textToDom( '' + ' ' + ' ' + ' ' + ''); parent.getInput('INPUT').connection.setShadowDom(xml2); assertInputHasBlock(parent, 'INPUT', true, '2'); assertInputHasBlock( parent.getInputTargetBlock('INPUT'), 'INPUT', true, 'b'); assertSerialization( parent, { 'type': 'row_block', 'id': 'id0', 'inputs': { 'INPUT': { 'shadow': { 'type': 'row_block', 'id': '2', 'inputs': { 'INPUT': { 'shadow': { 'type': 'row_block', 'id': 'b', }, }, }, }, }, }, }, '' + '' + '' + '' + '' + '' + '' + '' + '' ); }); test('Statement', function() { const parent = createStatementBlock(this.workspace); const xml1 = Blockly.Xml.textToDom( ''); parent.getInput('NAME').connection.setShadowDom(xml1); assertInputHasBlock(parent, 'NAME', true, '1'); const xml2 = Blockly.Xml.textToDom( ''); parent.getInput('NAME').connection.setShadowDom(xml2); assertInputHasBlock(parent, 'NAME', true, '2'); assertSerialization( parent, { 'type': 'statement_block', 'id': 'id0', 'inputs': { 'NAME': { 'shadow': { 'type': 'statement_block', 'id': '2', }, }, }, }, '' + '' + '' + '' + '' ); }); test('Multiple Statement', function() { const parent = createStatementBlock(this.workspace); const xml1 = Blockly.Xml.textToDom( '' + ' ' + ' ' + ' ' + ''); parent.getInput('NAME').connection.setShadowDom(xml1); assertInputHasBlock(parent, 'NAME', true, '1'); assertInputHasBlock( parent.getInputTargetBlock('NAME'), 'NAME', true, 'a'); const xml2 = Blockly.Xml.textToDom( '' + ' ' + ' ' + ' ' + ''); parent.getInput('NAME').connection.setShadowDom(xml2); assertInputHasBlock(parent, 'NAME', true, '2'); assertInputHasBlock( parent.getInputTargetBlock('NAME'), 'NAME', true, 'b'); assertSerialization( parent, { 'type': 'statement_block', 'id': 'id0', 'inputs': { 'NAME': { 'shadow': { 'type': 'statement_block', 'id': '2', 'inputs': { 'NAME': { 'shadow': { 'type': 'statement_block', 'id': 'b', }, }, }, }, }, }, }, '' + '' + '' + '' + '' + '' + '' + '' + '' ); }); test('Next', function() { const parent = createStackBlock(this.workspace); const xml1 = Blockly.Xml.textToDom(''); parent.nextConnection.setShadowDom(xml1); assertNextHasBlock(parent, true, '1'); const xml2 = Blockly.Xml.textToDom(''); parent.nextConnection.setShadowDom(xml2); assertNextHasBlock(parent, true, '2'); assertSerialization( parent, { 'type': 'stack_block', 'id': 'id0', 'next': { 'shadow': { 'type': 'stack_block', 'id': '2', }, }, }, '' + '' + '' + '' + '' ); }); test('Multiple Next', function() { const parent = createStackBlock(this.workspace); const xml1 = Blockly.Xml.textToDom( '' + ' ' + ' ' + ' ' + ''); parent.nextConnection.setShadowDom(xml1); assertNextHasBlock(parent, true, '1'); assertNextHasBlock(parent.getNextBlock(), true, 'a'); const xml2 = Blockly.Xml.textToDom( '' + ' ' + ' ' + ' ' + ''); parent.nextConnection.setShadowDom(xml2); assertNextHasBlock(parent, true, '2'); assertNextHasBlock(parent.getNextBlock(), true, 'b'); assertSerialization( parent, { 'type': 'stack_block', 'id': 'id0', 'next': { 'shadow': { 'type': 'stack_block', 'id': '2', 'next': { 'shadow': { 'type': 'stack_block', 'id': 'b', }, }, }, }, }, '' + '' + '' + '' + '' + '' + '' + '' + '' ); }); }); suite('Remove - No Block Connected', function() { // These are defined separately in each suite. function createRowBlock(workspace) { const block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom( '' + ' ' + ' ' + ' ' + '' ), workspace); return block; } function createStatementBlock(workspace) { const block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom( '' + ' ' + ' ' + ' ' + '' ), workspace); return block; } function createStackBlock(workspace) { const block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom( '' + ' ' + ' ' + ' ' + '' ), workspace); return block; } test('Value', function() { const parent = createRowBlock(this.workspace); parent.getInput('INPUT').connection.setShadowDom(null); assertInputNotHasBlock(parent, 'INPUT'); assertSerialization( parent, { 'type': 'row_block', 'id': 'id0', }, '' + '' ); }); test('Statement', function() { const parent = createStatementBlock(this.workspace); parent.getInput('NAME').connection.setShadowDom(null); assertInputNotHasBlock(parent, 'STATEMENT'); assertSerialization( parent, { 'type': 'statement_block', 'id': 'id0', }, '' + '' ); }); test('Next', function() { const parent = createStackBlock(this.workspace); parent.nextConnection.setShadowDom(null); assertNextNotHasBlock(parent); assertSerialization( parent, { 'type': 'stack_block', 'id': 'id0', }, '' + '' ); }); }); suite('Remove - Block Connected', function() { // These are defined separately in each suite. function createRowBlock(workspace) { const block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom( '' + ' ' + ' ' + ' ' + ' ' + '' ), workspace); return block; } function createStatementBlock(workspace) { const block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom( '' + ' ' + ' ' + ' ' + ' ' + '' ), workspace); return block; } function createStackBlock(workspace) { const block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom( '' + ' ' + ' ' + ' ' + ' ' + '' ), workspace); return block; } test('Value', function() { const parent = createRowBlock(this.workspace); parent.getInput('INPUT').connection.setShadowDom(null); assertInputHasBlock(parent, 'INPUT', false); parent.getInput('INPUT').connection.disconnect(); assertInputNotHasBlock(parent, 'INPUT'); assertSerialization( parent, { 'type': 'row_block', 'id': 'id0', }, '' + '' ); }); test('Statement', function() { const parent = createStatementBlock(this.workspace); parent.getInput('NAME').connection.setShadowDom(null); assertInputHasBlock(parent, 'NAME', false); parent.getInput('NAME').connection.disconnect(); assertInputNotHasBlock(parent, 'NAME'); assertSerialization( parent, { 'type': 'statement_block', 'id': 'id0', }, '' + '' ); }); test('Next', function() { const parent = createStackBlock(this.workspace); parent.nextConnection.setShadowDom(null); assertNextHasBlock(parent, false); parent.nextConnection.disconnect(); assertNextNotHasBlock(parent); assertSerialization( parent, { 'type': 'stack_block', 'id': 'id0', }, '' + '' ); }); }); suite('Add - Connect & Disconnect - Remove', function() { // These are defined separately in each suite. function createRowBlock(workspace) { const block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom( '' ), workspace); return block; } function createStatementBlock(workspace) { const block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom( '' ), workspace); return block; } function createStackBlock(workspace) { const block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom( '' ), workspace); return block; } test('Value', function() { const parent = createRowBlock(this.workspace); const xml = Blockly.Xml.textToDom( '' ); parent.getInput('INPUT').connection.setShadowDom(xml); assertInputHasBlock(parent, 'INPUT', true); const 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() { const parent = createRowBlock(this.workspace); const xml = Blockly.Xml.textToDom( '' + ' ' + ' ' + ' ' + '' ); parent.getInput('INPUT').connection.setShadowDom(xml); assertInputHasBlock(parent, 'INPUT', true); assertInputHasBlock( parent.getInputTargetBlock('INPUT'), 'INPUT', true); const 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() { const parent = createStatementBlock(this.workspace); const xml = Blockly.Xml.textToDom( '' ); parent.getInput('NAME').connection.setShadowDom(xml); assertInputHasBlock(parent, 'NAME', true); const child = createStatementBlock(this.workspace); parent.getInput('NAME').connection .connect(child.previousConnection); assertInputHasBlock(parent, 'NAME', false); parent.getInput('NAME').connection.disconnect(); assertInputHasBlock(parent, 'NAME', true); parent.getInput('NAME').connection.setShadowDom(null); assertInputNotHasBlock(parent, 'NAME'); }); test('Multiple Statement', function() { const parent = createStatementBlock(this.workspace); const xml = Blockly.Xml.textToDom( '' + ' ' + ' ' + ' ' + '' ); parent.getInput('NAME').connection.setShadowDom(xml); assertInputHasBlock(parent, 'NAME', true); assertInputHasBlock( parent.getInputTargetBlock('NAME'), 'NAME', true); const child = createStatementBlock(this.workspace); parent.getInput('NAME').connection .connect(child.previousConnection); assertInputHasBlock(parent, 'NAME', false); parent.getInput('NAME').connection.disconnect(); assertInputHasBlock(parent, 'NAME', true); assertInputHasBlock( parent.getInputTargetBlock('NAME'), 'NAME', true); parent.getInput('NAME').connection.setShadowDom(null); assertInputNotHasBlock(parent, 'NAME'); }); test('Next', function() { const parent = createStackBlock(this.workspace); const xml = Blockly.Xml.textToDom( '' ); parent.nextConnection.setShadowDom(xml); assertNextHasBlock(parent, true); const 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() { const parent = createStackBlock(this.workspace); const xml = Blockly.Xml.textToDom( '' + ' ' + ' ' + ' ' + '' ); parent.nextConnection.setShadowDom(xml); assertNextHasBlock(parent, true); assertNextHasBlock(parent.getNextBlock(), true); const 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); }); }); suite('Invalid', function() { test('Attach to output', function() { const block = this.workspace.newBlock('row_block'); chai.assert.throws(() => block.outputConnection.setShadowDom(Blockly.Xml.textToDom( ''))); }); test('Attach to previous', function() { const block = this.workspace.newBlock('stack_block'); chai.assert.throws(() => block.previousConnection.setShadowDom(Blockly.Xml.textToDom( ''))); }); test('Missing output', function() { const block = this.workspace.newBlock('row_block'); chai.assert.throws(() => block.outputConnection.setShadowDom(Blockly.Xml.textToDom( ''))); }); test('Missing previous', function() { const block = this.workspace.newBlock('stack_block'); chai.assert.throws(() => block.previousConnection.setShadowDom(Blockly.Xml.textToDom( ''))); }); test('Invalid connection checks, output', function() { const block = this.workspace.newBlock('logic_operation'); chai.assert.throws(() => block.getInput('A').connection.setShadowDom( Blockly.Xml.textToDom(''))); }); test('Invalid connection checks, previous', function() { Blockly.defineBlocksWithJsonArray([{ "type": "stack_checks_block", "message0": "", "previousStatement": "check 1", "nextStatement": "check 2", }]); const block = this.workspace.newBlock('stack_checks_block'); chai.assert.throws(() => block.nextConnection.setShadowDom(Blockly.Xml.textToDom( ''))); }); }); }); suite('setShadowState', function() { suite('Add - No Block Connected', function() { // These are defined separately in each suite. function createRowBlock(workspace) { return Blockly.serialization.blocks.append( {'type': 'row_block', 'id': 'id0'}, workspace); } function createStatementBlock(workspace) { return Blockly.serialization.blocks.append( {'type': 'statement_block', 'id': 'id0'}, workspace); } function createStackBlock(workspace) { return Blockly.serialization.blocks.append( {'type': 'stack_block', 'id': 'id0'}, workspace); } test('Value', function() { const parent = createRowBlock(this.workspace); parent.getInput('INPUT').connection .setShadowState({'type': 'row_block', 'id': 'id1'}); assertInputHasBlock(parent, 'INPUT', true); assertSerialization( parent, { 'type': 'row_block', 'id': 'id0', 'inputs': { 'INPUT': { 'shadow': { 'type': 'row_block', 'id': 'id1', }, }, }, }, '' + '' + '' + '' + '' ); }); test('Multiple Value', function() { const parent = createRowBlock(this.workspace); parent.getInput('INPUT').connection.setShadowState({ 'type': 'row_block', 'id': 'id1', 'inputs': { 'INPUT': { 'shadow': { 'type': 'row_block', 'id': 'id2', }, }, }, }); assertInputHasBlock(parent, 'INPUT', true); assertInputHasBlock( parent.getInputTargetBlock('INPUT'), 'INPUT', true); assertSerialization( parent, { 'type': 'row_block', 'id': 'id0', 'inputs': { 'INPUT': { 'shadow': { 'type': 'row_block', 'id': 'id1', 'inputs': { 'INPUT': { 'shadow': { 'type': 'row_block', 'id': 'id2', }, }, }, }, }, }, }, '' + '' + '' + '' + '' + '' + '' + '' + '' ); }); test('Statement', function() { const parent = createStatementBlock(this.workspace); parent.getInput('NAME').connection .setShadowState({'type': 'statement_block', 'id': 'id1'}); assertInputHasBlock(parent, 'NAME', true); assertSerialization( parent, { 'type': 'statement_block', 'id': 'id0', 'inputs': { 'NAME': { 'shadow': { 'type': 'statement_block', 'id': 'id1', }, }, }, }, '' + '' + '' + '' + '' ); }); test('Multiple Statment', function() { const parent = createStatementBlock(this.workspace); parent.getInput('NAME').connection.setShadowState({ 'type': 'statement_block', 'id': 'id1', 'inputs': { 'NAME': { 'shadow': { 'type': 'statement_block', 'id': 'id2', }, }, }, }); assertInputHasBlock(parent, 'NAME', true); assertInputHasBlock( parent.getInputTargetBlock('NAME'), 'NAME', true); assertSerialization( parent, { 'type': 'statement_block', 'id': 'id0', 'inputs': { 'NAME': { 'shadow': { 'type': 'statement_block', 'id': 'id1', 'inputs': { 'NAME': { 'shadow': { 'type': 'statement_block', 'id': 'id2', }, }, }, }, }, }, }, '' + '' + '' + '' + '' + '' + '' + '' + '' ); }); test('Next', function() { const parent = createStackBlock(this.workspace); parent.nextConnection .setShadowState({'type': 'stack_block', 'id': 'id1'}); assertNextHasBlock(parent, true); assertSerialization( parent, { 'type': 'stack_block', 'id': 'id0', 'next': { 'shadow': { 'type': 'stack_block', 'id': 'id1', }, }, }, '' + '' + '' + '' + '' ); }); test('Multiple Next', function() { const parent = createStackBlock(this.workspace); parent.nextConnection.setShadowState({ 'type': 'stack_block', 'id': 'id1', 'next': { 'shadow': { 'type': 'stack_block', 'id': 'id2', }, }, }); assertNextHasBlock(parent, true); assertNextHasBlock(parent.getNextBlock(), true); assertSerialization( parent, { 'type': 'stack_block', 'id': 'id0', 'next': { 'shadow': { 'type': 'stack_block', 'id': 'id1', 'next': { 'shadow': { 'type': 'stack_block', 'id': 'id2', }, }, }, }, }, '' + '' + '' + '' + '' + '' + '' + '' + '' ); }); }); suite('Add - With Block Connected', function() { // These are defined separately in each suite. function createRowBlocks(workspace) { return Blockly.serialization.blocks.append( { 'type': 'row_block', 'id': 'id0', 'inputs': { 'INPUT': { 'block': { 'type': 'row_block', 'id': 'idA', }, }, }, }, workspace); } function createStatementBlocks(workspace) { return Blockly.serialization.blocks.append( { 'type': 'statement_block', 'id': 'id0', 'inputs': { 'NAME': { 'block': { 'type': 'statement_block', 'id': 'idA', }, }, }, }, workspace); } function createStackBlocks(workspace) { return Blockly.serialization.blocks.append( { 'type': 'stack_block', 'id': 'id0', 'next': { 'block': { 'type': 'stack_block', 'id': 'idA', }, }, }, workspace); } test('Value', function() { const parent = createRowBlocks(this.workspace); parent.getInput('INPUT').connection .setShadowState({'type': 'row_block', 'id': 'id1'}); assertInputHasBlock(parent, 'INPUT', false); parent.getInput('INPUT').connection.disconnect(); assertInputHasBlock(parent, 'INPUT', true); assertSerialization( parent, { 'type': 'row_block', 'id': 'id0', 'inputs': { 'INPUT': { 'shadow': { 'type': 'row_block', 'id': 'id1', }, }, }, }, '' + '' + '' + '' + '' ); }); test('Multiple Value', function() { const parent = createRowBlocks(this.workspace); parent.getInput('INPUT').connection.setShadowState( { 'type': 'row_block', 'id': 'id1', 'inputs': { 'INPUT': { 'shadow': { 'type': 'row_block', 'id': 'id2', }, }, }, } ); assertInputHasBlock(parent, 'INPUT', false); assertInputNotHasBlock(parent.getInputTargetBlock('INPUT'), 'INPUT'); parent.getInput('INPUT').connection.disconnect(); assertInputHasBlock(parent, 'INPUT', true); assertInputHasBlock( parent.getInputTargetBlock('INPUT'), 'INPUT', true); assertSerialization( parent, { 'type': 'row_block', 'id': 'id0', 'inputs': { 'INPUT': { 'shadow': { 'type': 'row_block', 'id': 'id1', 'inputs': { 'INPUT': { 'shadow': { 'type': 'row_block', 'id': 'id2', }, }, }, }, }, }, }, '' + '' + '' + '' + '' + '' + '' + '' + '' ); }); test('Statement', function() { const parent = createStatementBlocks(this.workspace); parent.getInput('NAME').connection .setShadowState({'type': 'statement_block', 'id': 'id1'}); assertInputHasBlock(parent, 'NAME', false); parent.getInput('NAME').connection.disconnect(); assertInputHasBlock(parent, 'NAME', true); assertSerialization( parent, { 'type': 'statement_block', 'id': 'id0', 'inputs': { 'NAME': { 'shadow': { 'type': 'statement_block', 'id': 'id1', }, }, }, }, '' + '' + '' + '' + '' ); }); test('Multiple Statement', function() { const parent = createStatementBlocks(this.workspace); parent.getInput('NAME').connection.setShadowState( { 'type': 'statement_block', 'id': 'id1', 'inputs': { 'NAME': { 'shadow': { 'type': 'statement_block', 'id': 'id2', }, }, }, } ); assertInputHasBlock(parent, 'NAME', false); assertInputNotHasBlock( parent.getInputTargetBlock('NAME'), 'NAME'); parent.getInput('NAME').connection.disconnect(); assertInputHasBlock(parent, 'NAME', true); assertInputHasBlock( parent.getInputTargetBlock('NAME'), 'NAME', true); assertSerialization( parent, { 'type': 'statement_block', 'id': 'id0', 'inputs': { 'NAME': { 'shadow': { 'type': 'statement_block', 'id': 'id1', 'inputs': { 'NAME': { 'shadow': { 'type': 'statement_block', 'id': 'id2', }, }, }, }, }, }, }, '' + '' + '' + '' + '' + '' + '' + '' + '' ); }); test('Next', function() { const parent = createStackBlocks(this.workspace); parent.nextConnection .setShadowState({'type': 'stack_block', 'id': 'id1'}); assertNextHasBlock(parent, false); parent.nextConnection.disconnect(); assertNextHasBlock(parent, true); assertSerialization( parent, { 'type': 'stack_block', 'id': 'id0', 'next': { 'shadow': { 'type': 'stack_block', 'id': 'id1', }, }, }, '' + '' + '' + '' + '' ); }); test('Multiple Next', function() { const parent = createStackBlocks(this.workspace); parent.nextConnection.setShadowState( { 'type': 'stack_block', 'id': 'id1', 'next': { 'shadow': { 'type': 'stack_block', 'id': 'id2', }, }, } ); assertNextHasBlock(parent, false); assertNextNotHasBlock(parent.getNextBlock()); parent.nextConnection.disconnect(); assertNextHasBlock(parent, true); assertNextHasBlock(parent.getNextBlock(), true); assertSerialization( parent, { 'type': 'stack_block', 'id': 'id0', 'next': { 'shadow': { 'type': 'stack_block', 'id': 'id1', 'next': { 'shadow': { 'type': 'stack_block', 'id': 'id2', }, }, }, }, }, '' + '' + '' + '' + '' + '' + '' + '' + '' ); }); }); suite('Add - With Shadow Connected', function() { // These are defined separately in each suite. function createRowBlock(workspace) { return Blockly.serialization.blocks.append( {'type': 'row_block', 'id': 'id0'}, workspace); } function createStatementBlock(workspace) { return Blockly.serialization.blocks.append( {'type': 'statement_block', 'id': 'id0'}, workspace); } function createStackBlock(workspace) { return Blockly.serialization.blocks.append( {'type': 'stack_block', 'id': 'id0'}, workspace); } test('Value', function() { const parent = createRowBlock(this.workspace); parent.getInput('INPUT').connection .setShadowState({'type': 'row_block', 'id': '1'}); assertInputHasBlock(parent, 'INPUT', true, '1'); parent.getInput('INPUT').connection .setShadowState({'type': 'row_block', 'id': '2'}); assertInputHasBlock(parent, 'INPUT', true, '2'); assertSerialization( parent, { 'type': 'row_block', 'id': 'id0', 'inputs': { 'INPUT': { 'shadow': { 'type': 'row_block', 'id': '2', }, }, }, }, '' + '' + '' + '' + '' ); }); test('Multiple Value', function() { const parent = createRowBlock(this.workspace); parent.getInput('INPUT').connection.setShadowState( { 'type': 'row_block', 'id': '1', 'inputs': { 'INPUT': { 'shadow': { 'type': 'row_block', 'id': 'a', }, }, }, } ); assertInputHasBlock(parent, 'INPUT', true, '1'); assertInputHasBlock( parent.getInputTargetBlock('INPUT'), 'INPUT', true, 'a'); parent.getInput('INPUT').connection.setShadowState( { 'type': 'row_block', 'id': '2', 'inputs': { 'INPUT': { 'shadow': { 'type': 'row_block', 'id': 'b', }, }, }, } ); assertInputHasBlock(parent, 'INPUT', true, '2'); assertInputHasBlock( parent.getInputTargetBlock('INPUT'), 'INPUT', true, 'b'); assertSerialization( parent, { 'type': 'row_block', 'id': 'id0', 'inputs': { 'INPUT': { 'shadow': { 'type': 'row_block', 'id': '2', 'inputs': { 'INPUT': { 'shadow': { 'type': 'row_block', 'id': 'b', }, }, }, }, }, }, }, '' + '' + '' + '' + '' + '' + '' + '' + '' ); }); test('Statement', function() { const parent = createStatementBlock(this.workspace); parent.getInput('NAME').connection .setShadowState({'type': 'statement_block', 'id': '1'}); assertInputHasBlock(parent, 'NAME', true, '1'); parent.getInput('NAME').connection .setShadowState({'type': 'statement_block', 'id': '2'}); assertInputHasBlock(parent, 'NAME', true, '2'); assertSerialization( parent, { 'type': 'statement_block', 'id': 'id0', 'inputs': { 'NAME': { 'shadow': { 'type': 'statement_block', 'id': '2', }, }, }, }, '' + '' + '' + '' + '' ); }); test('Multiple Statement', function() { const parent = createStatementBlock(this.workspace); parent.getInput('NAME').connection.setShadowState( { 'type': 'statement_block', 'id': '1', 'inputs': { 'NAME': { 'shadow': { 'type': 'statement_block', 'id': 'a', }, }, }, } ); assertInputHasBlock(parent, 'NAME', true, '1'); assertInputHasBlock( parent.getInputTargetBlock('NAME'), 'NAME', true, 'a'); parent.getInput('NAME').connection.setShadowState( { 'type': 'statement_block', 'id': '2', 'inputs': { 'NAME': { 'shadow': { 'type': 'statement_block', 'id': 'b', }, }, }, } ); assertInputHasBlock(parent, 'NAME', true, '2'); assertInputHasBlock( parent.getInputTargetBlock('NAME'), 'NAME', true, 'b'); assertSerialization( parent, { 'type': 'statement_block', 'id': 'id0', 'inputs': { 'NAME': { 'shadow': { 'type': 'statement_block', 'id': '2', 'inputs': { 'NAME': { 'shadow': { 'type': 'statement_block', 'id': 'b', }, }, }, }, }, }, }, '' + '' + '' + '' + '' + '' + '' + '' + '' ); }); test('Next', function() { const parent = createStackBlock(this.workspace); parent.nextConnection .setShadowState({'type': 'stack_block', 'id': '1'}); assertNextHasBlock(parent, true, '1'); parent.nextConnection .setShadowState({'type': 'stack_block', 'id': '2'}); assertNextHasBlock(parent, true, '2'); assertSerialization( parent, { 'type': 'stack_block', 'id': 'id0', 'next': { 'shadow': { 'type': 'stack_block', 'id': '2', }, }, }, '' + '' + '' + '' + '' ); }); test('Multiple Next', function() { const parent = createStackBlock(this.workspace); parent.nextConnection.setShadowState( { 'type': 'stack_block', 'id': '1', 'next': { 'shadow': { 'type': 'stack_block', 'id': 'a', }, }, } ); assertNextHasBlock(parent, true, '1'); assertNextHasBlock(parent.getNextBlock(), true, 'a'); parent.nextConnection.setShadowState( { 'type': 'stack_block', 'id': '2', 'next': { 'shadow': { 'type': 'stack_block', 'id': 'b', }, }, } ); assertNextHasBlock(parent, true, '2'); assertNextHasBlock(parent.getNextBlock(), true, 'b'); assertSerialization( parent, { 'type': 'stack_block', 'id': 'id0', 'next': { 'shadow': { 'type': 'stack_block', 'id': '2', 'next': { 'shadow': { 'type': 'stack_block', 'id': 'b', }, }, }, }, }, '' + '' + '' + '' + '' + '' + '' + '' + '' ); }); }); suite('Remove - No Block Connected', function() { // These are defined separately in each suite. function createRowBlocks(workspace) { return Blockly.serialization.blocks.append( { 'type': 'row_block', 'id': 'id0', 'inputs': { 'INPUT': { 'shadow': { 'type': 'row_block', 'id': 'id1', }, }, }, }, workspace); } function createStatementBlocks(workspace) { return Blockly.serialization.blocks.append( { 'type': 'statement_block', 'id': 'id0', 'inputs': { 'NAME': { 'shadow': { 'type': 'statement_block', 'id': 'id1', }, }, }, }, workspace); } function createStackBlocks(workspace) { return Blockly.serialization.blocks.append( { 'type': 'stack_block', 'id': 'id0', 'next': { 'shadow': { 'type': 'stack_block', 'id': 'id1', }, }, }, workspace); } test('Value', function() { const parent = createRowBlocks(this.workspace); parent.getInput('INPUT').connection.setShadowState(null); assertInputNotHasBlock(parent, 'INPUT'); assertSerialization( parent, { 'type': 'row_block', 'id': 'id0', }, '' + '' ); }); test('Statement', function() { const parent = createStatementBlocks(this.workspace); parent.getInput('NAME').connection.setShadowState(null); assertInputNotHasBlock(parent, 'NAME'); assertSerialization( parent, { 'type': 'statement_block', 'id': 'id0', }, '' + '' ); }); test('Next', function() { const parent = createStackBlocks(this.workspace); parent.nextConnection.setShadowState(null); assertNextNotHasBlock(parent); assertSerialization( parent, { 'type': 'stack_block', 'id': 'id0', }, '' + '' ); }); }); suite('Remove - Block Connected', function() { // These are defined separately in each suite. function createRowBlocks(workspace) { return Blockly.serialization.blocks.append( { 'type': 'row_block', 'id': 'id0', 'inputs': { 'INPUT': { 'shadow': { 'type': 'row_block', 'id': 'id1', }, 'block': { 'type': 'row_block', 'id': 'id2', }, }, }, }, workspace); } function createStatementBlocks(workspace) { return Blockly.serialization.blocks.append( { 'type': 'statement_block', 'id': 'id0', 'inputs': { 'NAME': { 'shadow': { 'type': 'statement_block', 'id': 'id1', }, 'block': { 'type': 'statement_block', 'id': 'id2', }, }, }, }, workspace); } function createStackBlocks(workspace) { return Blockly.serialization.blocks.append( { 'type': 'stack_block', 'id': 'id0', 'next': { 'shadow': { 'type': 'stack_block', 'id': 'id1', }, 'block': { 'type': 'stack_block', 'id': 'id2', }, }, }, workspace); } test('Value', function() { const parent = createRowBlocks(this.workspace); parent.getInput('INPUT').connection.setShadowState(null); assertInputHasBlock(parent, 'INPUT', false); parent.getInput('INPUT').connection.disconnect(); assertInputNotHasBlock(parent, 'INPUT'); assertSerialization( parent, { 'type': 'row_block', 'id': 'id0', }, '' + '' ); }); test('Statement', function() { const parent = createStatementBlocks(this.workspace); parent.getInput('NAME').connection.setShadowState(null); assertInputHasBlock(parent, 'NAME', false); parent.getInput('NAME').connection.disconnect(); assertInputNotHasBlock(parent, 'NAME'); assertSerialization( parent, { 'type': 'statement_block', 'id': 'id0', }, '' + '' ); }); test('Next', function() { const parent = createStackBlocks(this.workspace); parent.nextConnection.setShadowState(null); assertNextHasBlock(parent, false); parent.nextConnection.disconnect(); assertNextNotHasBlock(parent); assertSerialization( parent, { 'type': 'stack_block', 'id': 'id0', }, '' + '' ); }); }); suite('Add - Connect & Disconnect - Remove', function() { // These are defined separately in each suite. function createRowBlock(workspace) { return Blockly.serialization.blocks.append( {'type': 'row_block'}, workspace); } function createStatementBlock(workspace) { return Blockly.serialization.blocks.append( {'type': 'statement_block'}, workspace); } function createStackBlock(workspace) { return Blockly.serialization.blocks.append( {'type': 'stack_block'}, workspace); } test('Value', function() { const parent = createRowBlock(this.workspace); parent.getInput('INPUT').connection .setShadowState({'type': 'row_block'}); assertInputHasBlock(parent, 'INPUT', true); const 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.setShadowState(null); assertInputNotHasBlock(parent, 'INPUT'); }); test('Multiple Value', function() { const parent = createRowBlock(this.workspace); parent.getInput('INPUT').connection.setShadowState({ 'type': 'row_block', 'inputs': { 'INPUT': { 'shadow': { 'type': 'row_block', }, }, }, }); assertInputHasBlock(parent, 'INPUT', true); assertInputHasBlock( parent.getInputTargetBlock('INPUT'), 'INPUT', true); const 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.setShadowState(null); assertInputNotHasBlock(parent, 'INPUT'); }); test('Statement', function() { const parent = createStatementBlock(this.workspace); parent.getInput('NAME').connection .setShadowState({'type': 'statement_block'}); assertInputHasBlock(parent, 'NAME', true); const child = createStatementBlock(this.workspace); parent.getInput('NAME').connection .connect(child.previousConnection); assertInputHasBlock(parent, 'NAME', false); parent.getInput('NAME').connection.disconnect(); assertInputHasBlock(parent, 'NAME', true); parent.getInput('NAME').connection.setShadowState(null); assertInputNotHasBlock(parent, 'NAME'); }); test('Multiple Statement', function() { const parent = createStatementBlock(this.workspace); parent.getInput('NAME').connection.setShadowState({ 'type': 'statement_block', 'inputs': { 'NAME': { 'shadow': { 'type': 'statement_block', }, }, }, }); assertInputHasBlock(parent, 'NAME', true); assertInputHasBlock( parent.getInputTargetBlock('NAME'), 'NAME', true); const child = createStatementBlock(this.workspace); parent.getInput('NAME').connection .connect(child.previousConnection); assertInputHasBlock(parent, 'NAME', false); parent.getInput('NAME').connection.disconnect(); assertInputHasBlock(parent, 'NAME', true); assertInputHasBlock( parent.getInputTargetBlock('NAME'), 'NAME', true); parent.getInput('NAME').connection.setShadowState(null); assertInputNotHasBlock(parent, 'NAME'); }); test('Next', function() { const parent = createStackBlock(this.workspace); parent.nextConnection.setShadowState({'type': 'stack_block'}); const child = createStatementBlock(this.workspace); parent.nextConnection.connect(child.previousConnection); assertNextHasBlock(parent, false); parent.nextConnection.disconnect(); assertNextHasBlock(parent, true); parent.nextConnection.setShadowState(null); assertNextNotHasBlock(parent); }); test('Multiple Next', function() { const parent = createStackBlock(this.workspace); parent.nextConnection.setShadowState({ 'type': 'stack_block', 'next': { 'shadow': { 'type': 'stack_block', }, }, }); assertNextHasBlock(parent, true); assertNextHasBlock(parent.getNextBlock(), true); const child = createStatementBlock(this.workspace); parent.nextConnection.connect(child.previousConnection); assertNextHasBlock(parent, false); parent.nextConnection.disconnect(); assertNextHasBlock(parent, true); assertNextHasBlock(parent.getNextBlock(), true); parent.nextConnection.setShadowState(null); assertNextNotHasBlock(parent); }); }); suite('Invalid', function() { test('Attach to output', function() { const block = this.workspace.newBlock('row_block'); chai.assert.throws(() => block.outputConnection.setShadowState({'type': 'row_block'})); }); test('Attach to previous', function() { const block = this.workspace.newBlock('stack_block'); chai.assert.throws(() => block.previousConnection.setShadowState( {'type': 'stack_block'})); }); test('Missing output', function() { const block = this.workspace.newBlock('row_block'); chai.assert.throws(() => block.outputConnection.setShadowState({'type': 'stack_block'})); }); test('Missing previous', function() { const block = this.workspace.newBlock('stack_block'); chai.assert.throws(() => block.previousConnection.setShadowState({'type': 'row_block'})); }); test('Invalid connection checks, output', function() { const block = this.workspace.newBlock('logic_operation'); chai.assert.throws(() => block.getInput('A').connection.setShadowState( {'type': 'math_number'})); }); test('Invalid connection checks, previous', function() { Blockly.defineBlocksWithJsonArray([{ "type": "stack_checks_block", "message0": "", "previousStatement": "check 1", "nextStatement": "check 2", }]); const block = this.workspace.newBlock('stack_checks_block'); chai.assert.throws(() => block.nextConnection.setShadowState( {'type': 'stack_checks_block'})); }); }); }); }); }); }); suite('Connect', function() { setup(function() { this.workspace = new Blockly.Workspace(); Blockly.defineBlocksWithJsonArray([ { "type": "stack_block", "message0": "%1", "args0": [ { "type": "field_input", "name": "FIELD", "text": "default", }, ], "previousStatement": 'check1', "nextStatement": 'check1', }, { "type": "stack_block_1to2", "message0": "", "previousStatement": 'check1', "nextStatement": 'check2', }, { "type": "stack_block_2to1", "message0": "", "previousStatement": 'check2', "nextStatement": 'check1', }, { "type": "stack_block_noend", "message0": "", "previousStatement": 'check1', }, { "type": "row_block", "message0": "%1 %2", "args0": [ { "type": "field_input", "name": "FIELD", "text": "default", }, { "type": "input_value", "name": "INPUT", "check": 'check1', }, ], "output": 'check1', }, { "type": "row_block_1to2", "message0": "%1", "args0": [ { "type": "input_value", "name": "INPUT", "check": 'check1', }, ], "output": 'check2', }, { "type": "row_block_2to1", "message0": "%1", "args0": [ { "type": "input_value", "name": "INPUT", "check": 'check2', }, ], "output": 'check1', }, { "type": "row_block_noend", "message0": "", "output": 'check1', }, { "type": "row_block_multiple_inputs", "message0": "%1 %2", "args0": [ { "type": "input_value", "name": "INPUT", "check": 'check1', }, { "type": "input_value", "name": "INPUT2", "check": 'check1', }, ], "output": 'check1', }, { 'type': 'output_to_statements', 'message0': '%1 %2', 'args0': [ { 'type': 'input_statement', 'name': 'INPUT', 'check': 'check1', }, { 'type': 'input_statement', 'name': 'INPUT2', 'check': 'check1', }, ], 'output': 'check1', }, { "type": "statement_block", "message0": "%1 %2", "args0": [ { "type": "field_input", "name": "FIELD", "text": "default", }, { "type": "input_statement", "name": "NAME", "check": 'check1', }, ], "previousStatement": 'check1', "nextStatement": 'check1', }, { "type": "statement_block_1to2", "message0": "%1", "args0": [ { "type": "input_statement", "name": "NAME", "check": 'check1', }, ], "previousStatement": 'check1', "nextStatement": 'check2', }, { "type": "statement_block_2to1", "message0": "%1", "args0": [ { "type": "input_statement", "name": "NAME", "check": 'check2', }, ], "previousStatement": 'check2', "nextStatement": 'check1', }, { "type": "statement_block_noend", "message0": "%1", "args0": [ { "type": "input_statement", "name": "NAME", "check": 'check1', }, ], "previousStatement": 'check1', }, ]); // Used to make sure we don't get stray shadow blocks or anything. this.assertBlockCount = function(count) { chai.assert.equal(this.workspace.getAllBlocks().length, count); }; }); suite('Disconnect from old parent', function() { test('Value', function() { const oldParent = this.workspace.newBlock('row_block'); const newParent = this.workspace.newBlock('row_block'); const child = this.workspace.newBlock('row_block'); oldParent.getInput('INPUT').connection.connect(child.outputConnection); newParent.getInput('INPUT').connection.connect(child.outputConnection); chai.assert.isFalse( oldParent.getInput('INPUT').connection.isConnected()); this.assertBlockCount(3); }); test('Statement', function() { const oldParent = this.workspace.newBlock('statement_block'); const newParent = this.workspace.newBlock('statement_block'); const child = this.workspace.newBlock('stack_block'); oldParent.getInput('NAME').connection .connect(child.previousConnection); newParent.getInput('NAME').connection .connect(child.previousConnection); chai.assert.isFalse( oldParent.getInput('NAME').connection.isConnected()); this.assertBlockCount(3); }); test('Next', function() { const oldParent = this.workspace.newBlock('stack_block'); const newParent = this.workspace.newBlock('stack_block'); const child = this.workspace.newBlock('stack_block'); oldParent.nextConnection.connect(child.previousConnection); newParent.nextConnection.connect(child.previousConnection); chai.assert.isFalse(oldParent.nextConnection.isConnected()); this.assertBlockCount(3); }); }); suite('Shadow dissolves', function() { test('Value', function() { const newParent = this.workspace.newBlock('row_block'); const child = this.workspace.newBlock('row_block'); const xml = Blockly.Xml.textToDom( '' ); newParent.getInput('INPUT').connection.setShadowDom(xml); chai.assert.isTrue(newParent.getInputTargetBlock('INPUT').isShadow()); newParent.getInput('INPUT').connection.connect(child.outputConnection); chai.assert.isFalse(newParent.getInputTargetBlock('INPUT').isShadow()); this.assertBlockCount(2); }); test('Statement', function() { const newParent = this.workspace.newBlock('statement_block'); const child = this.workspace.newBlock('stack_block'); const xml = Blockly.Xml.textToDom( '' ); newParent.getInput('NAME').connection.setShadowDom(xml); chai.assert.isTrue( newParent.getInputTargetBlock('NAME').isShadow()); newParent.getInput('NAME').connection .connect(child.previousConnection); chai.assert.isFalse( newParent.getInputTargetBlock('NAME').isShadow()); this.assertBlockCount(2); }); test('Next', function() { const newParent = this.workspace.newBlock('stack_block'); const child = this.workspace.newBlock('stack_block'); const xml = Blockly.Xml.textToDom( '' ); newParent.nextConnection.setShadowDom(xml); chai.assert.isTrue(newParent.getNextBlock().isShadow()); newParent.nextConnection.connect(child.previousConnection); chai.assert.isFalse(newParent.getNextBlock().isShadow()); this.assertBlockCount(2); }); }); suite('Saving shadow values', function() { test('Value', function() { const newParent = this.workspace.newBlock('row_block'); const child = this.workspace.newBlock('row_block'); const xml = Blockly.Xml.textToDom( '' ); newParent.getInput('INPUT').connection.setShadowDom(xml); newParent.getInputTargetBlock('INPUT').setFieldValue('new', 'FIELD'); newParent.getInput('INPUT').connection.connect(child.outputConnection); newParent.getInput('INPUT').connection.disconnect(); const target = newParent.getInputTargetBlock('INPUT'); chai.assert.isTrue(target.isShadow()); chai.assert.equal(target.getFieldValue('FIELD'), 'new'); this.assertBlockCount(3); }); test('Statement', function() { const newParent = this.workspace.newBlock('statement_block'); const child = this.workspace.newBlock('stack_block'); const xml = Blockly.Xml.textToDom( '' ); newParent.getInput('NAME').connection.setShadowDom(xml); newParent.getInputTargetBlock('NAME') .setFieldValue('new', 'FIELD'); newParent.getInput('NAME').connection .connect(child.previousConnection); newParent.getInput('NAME').connection.disconnect(); const target = newParent.getInputTargetBlock('NAME'); chai.assert.isTrue(target.isShadow()); chai.assert.equal(target.getFieldValue('FIELD'), 'new'); this.assertBlockCount(3); }); test('Next', function() { const newParent = this.workspace.newBlock('stack_block'); const child = this.workspace.newBlock('stack_block'); const xml = Blockly.Xml.textToDom( '' ); newParent.nextConnection.setShadowDom(xml); newParent.getNextBlock().setFieldValue('new', 'FIELD'); newParent.nextConnection.connect(child.previousConnection); newParent.nextConnection.disconnect(); const target = newParent.getNextBlock(); chai.assert.isTrue(target.isShadow()); chai.assert.equal(target.getFieldValue('FIELD'), 'new'); this.assertBlockCount(3); }); }); suite('Reattach or bump orphan', function() { suite('Value', function() { suite('No available spots', function() { test('No connection', function() { const parent = this.workspace.newBlock('row_block'); const oldChild = this.workspace.newBlock('row_block'); const newChild = this.workspace.newBlock('row_block_noend'); parent.getInput('INPUT').connection .connect(oldChild.outputConnection); parent.getInput('INPUT').connection .connect(newChild.outputConnection); chai.assert.isTrue( parent.getInput('INPUT').connection.isConnected()); chai.assert.equal( parent.getInputTargetBlock('INPUT'), newChild); chai.assert.isFalse( oldChild.outputConnection.isConnected()); }); test('All statements', function() { const parent = this.workspace.newBlock('row_block'); const oldChild = this.workspace.newBlock('row_block'); const newChild = this.workspace.newBlock('output_to_statements'); parent.getInput('INPUT').connection .connect(oldChild.outputConnection); parent.getInput('INPUT').connection .connect(newChild.outputConnection); chai.assert.isTrue( parent.getInput('INPUT').connection.isConnected()); chai.assert.equal( parent.getInputTargetBlock('INPUT'), newChild); chai.assert.isFalse( oldChild.outputConnection.isConnected()); }); test('Bad checks', function() { const parent = this.workspace.newBlock('row_block'); const oldChild = this.workspace.newBlock('row_block'); const newChild = this.workspace.newBlock('row_block_2to1'); parent.getInput('INPUT').connection .connect(oldChild.outputConnection); parent.getInput('INPUT').connection .connect(newChild.outputConnection); chai.assert.isTrue( parent.getInput('INPUT').connection.isConnected()); chai.assert.equal( parent.getInputTargetBlock('INPUT'), newChild); chai.assert.isFalse( oldChild.outputConnection.isConnected()); }); test('Through different types', function() { const parent = this.workspace.newBlock('row_block'); const oldChild = this.workspace.newBlock('row_block'); const newChild = this.workspace.newBlock('row_block_2to1'); const otherChild = this.workspace.newBlock('row_block_1to2'); parent.getInput('INPUT').connection .connect(oldChild.outputConnection); newChild.getInput('INPUT').connection .connect(otherChild.outputConnection); parent.getInput('INPUT').connection .connect(newChild.outputConnection); chai.assert.isTrue( parent.getInput('INPUT').connection.isConnected()); chai.assert.equal( parent.getInputTargetBlock('INPUT'), newChild); chai.assert.isFalse( oldChild.outputConnection.isConnected()); }); }); suite('Multiple available spots', function() { suite('No shadows', function() { test('Top block', function() { const parent = this.workspace.newBlock('row_block'); const oldChild = this.workspace.newBlock('row_block'); const newChild = this.workspace.newBlock( 'row_block_multiple_inputs'); parent.getInput('INPUT').connection .connect(oldChild.outputConnection); parent.getInput('INPUT').connection .connect(newChild.outputConnection); chai.assert.isTrue( parent.getInput('INPUT').connection.isConnected()); chai.assert.equal( parent.getInputTargetBlock('INPUT'), newChild); chai.assert.isFalse( oldChild.outputConnection.isConnected()); }); test('Child blocks', function() { const parent = this.workspace.newBlock('row_block'); const oldChild = this.workspace.newBlock('row_block'); const newChild = this.workspace.newBlock( 'row_block_multiple_inputs'); const childX = this.workspace.newBlock('row_block'); const childY = this.workspace.newBlock('row_block'); parent.getInput('INPUT').connection .connect(oldChild.outputConnection); newChild.getInput('INPUT').connection .connect(childX.outputConnection); newChild.getInput('INPUT2').connection .connect(childY.outputConnection); parent.getInput('INPUT').connection .connect(newChild.outputConnection); chai.assert.isTrue( parent.getInput('INPUT').connection.isConnected()); chai.assert.equal( parent.getInputTargetBlock('INPUT'), newChild); chai.assert.isFalse( oldChild.outputConnection.isConnected()); }); test('Spots filled', function() { const parent = this.workspace.newBlock('row_block'); const oldChild = this.workspace.newBlock('row_block'); const newChild = this.workspace.newBlock( 'row_block_multiple_inputs'); const otherChild = this.workspace.newBlock('row_block_noend'); parent.getInput('INPUT').connection .connect(oldChild.outputConnection); newChild.getInput('INPUT').connection .connect(otherChild.outputConnection); parent.getInput('INPUT').connection .connect(newChild.outputConnection); chai.assert.isTrue( parent.getInput('INPUT').connection.isConnected()); chai.assert.equal( parent.getInputTargetBlock('INPUT'), newChild); chai.assert.isFalse( oldChild.outputConnection.isConnected()); }); }); suite('Shadows', function() { test('Top block', function() { const parent = this.workspace.newBlock('row_block'); const oldChild = this.workspace.newBlock('row_block'); const newChild = this.workspace.newBlock( 'row_block_multiple_inputs'); parent.getInput('INPUT').connection .connect(oldChild.outputConnection); newChild.getInput('INPUT').connection.setShadowDom( Blockly.Xml.textToDom('') .firstChild); newChild.getInput('INPUT2').connection.setShadowDom( Blockly.Xml.textToDom('') .firstChild); parent.getInput('INPUT').connection .connect(newChild.outputConnection); chai.assert.isTrue( parent.getInput('INPUT').connection.isConnected()); chai.assert.equal( parent.getInputTargetBlock('INPUT'), newChild); chai.assert.isFalse( oldChild.outputConnection.isConnected()); }); test('Child blocks', function() { const parent = this.workspace.newBlock('row_block'); const oldChild = this.workspace.newBlock('row_block'); const newChild = this.workspace.newBlock( 'row_block_multiple_inputs'); const childX = this.workspace.newBlock('row_block'); const childY = this.workspace.newBlock('row_block'); parent.getInput('INPUT').connection .connect(oldChild.outputConnection); newChild.getInput('INPUT').connection .connect(childX.outputConnection); newChild.getInput('INPUT2').connection .connect(childY.outputConnection); childX.getInput('INPUT').connection.setShadowDom( Blockly.Xml.textToDom('') .firstChild); childY.getInput('INPUT').connection.setShadowDom( Blockly.Xml.textToDom('') .firstChild); parent.getInput('INPUT').connection .connect(newChild.outputConnection); chai.assert.isTrue( parent.getInput('INPUT').connection.isConnected()); chai.assert.equal( parent.getInputTargetBlock('INPUT'), newChild); chai.assert.isFalse( oldChild.outputConnection.isConnected()); }); test('Spots filled', function() { const parent = this.workspace.newBlock('row_block'); const oldChild = this.workspace.newBlock('row_block'); const newChild = this.workspace.newBlock( 'row_block_multiple_inputs'); const otherChild = this.workspace.newBlock('row_block_noend'); parent.getInput('INPUT').connection .connect(oldChild.outputConnection); newChild.getInput('INPUT').connection .connect(otherChild.outputConnection); newChild.getInput('INPUT2').connection.setShadowDom( Blockly.Xml.textToDom('') .firstChild); parent.getInput('INPUT').connection .connect(newChild.outputConnection); chai.assert.isTrue( parent.getInput('INPUT').connection.isConnected()); chai.assert.equal( parent.getInputTargetBlock('INPUT'), newChild); chai.assert.isFalse( oldChild.outputConnection.isConnected()); }); }); }); suite('Single available spot', function() { test('No shadows', function() { const parent = this.workspace.newBlock('row_block'); const oldChild = this.workspace.newBlock('row_block'); const newChild = this.workspace.newBlock('row_block'); parent.getInput('INPUT').connection .connect(oldChild.outputConnection); parent.getInput('INPUT').connection .connect(newChild.outputConnection); chai.assert.isTrue( parent.getInput('INPUT').connection.isConnected()); chai.assert.equal( parent.getInputTargetBlock('INPUT'), newChild); chai.assert.isTrue( newChild.getInput('INPUT').connection.isConnected()); chai.assert.equal( newChild.getInputTargetBlock('INPUT'), oldChild); }); test('Shadows', function() { const parent = this.workspace.newBlock('row_block'); const oldChild = this.workspace.newBlock('row_block'); const newChild = this.workspace.newBlock('row_block'); parent.getInput('INPUT').connection .connect(oldChild.outputConnection); newChild.getInput('INPUT').connection.setShadowDom( Blockly.Xml.textToDom('') .firstChild); parent.getInput('INPUT').connection .connect(newChild.outputConnection); chai.assert.isTrue( parent.getInput('INPUT').connection.isConnected()); chai.assert.equal( parent.getInputTargetBlock('INPUT'), newChild); chai.assert.isTrue( newChild.getInput('INPUT').connection.isConnected()); chai.assert.equal( newChild.getInputTargetBlock('INPUT'), oldChild); }); }); }); suite('Statement', function() { suite('No shadows', function() { test('Simple', function() { const parent = this.workspace.newBlock('statement_block'); const oldChild = this.workspace.newBlock('stack_block'); const newChild = this.workspace.newBlock('stack_block'); parent.getInput('NAME').connection .connect(oldChild.previousConnection); parent.getInput('NAME').connection .connect(newChild.previousConnection); chai.assert.isTrue( parent.getInput('NAME').connection.isConnected()); chai.assert.equal( parent.getInputTargetBlock('NAME'), newChild); chai.assert.isTrue(newChild.nextConnection.isConnected()); chai.assert.equal(newChild.getNextBlock(), oldChild); this.assertBlockCount(3); }); test('Bad check in between', function() { const parent = this.workspace.newBlock('statement_block'); const oldChild = this.workspace.newBlock('stack_block'); const newChild1 = this.workspace.newBlock('stack_block_1to2'); const newChild2 = this.workspace.newBlock('stack_block_2to1'); parent.getInput('NAME').connection .connect(oldChild.previousConnection); newChild1.nextConnection.connect(newChild2.previousConnection); parent.getInput('NAME').connection .connect(newChild1.previousConnection); chai.assert.isTrue( parent.getInput('NAME').connection.isConnected()); chai.assert.equal( parent.getInputTargetBlock('NAME'), newChild1); chai.assert.isTrue(newChild2.nextConnection.isConnected()); chai.assert.equal(newChild2.getNextBlock(), oldChild); this.assertBlockCount(4); }); test('Bad check at end', function() { const parent = this.workspace.newBlock('statement_block'); const oldChild = this.workspace.newBlock('stack_block'); const newChild = this.workspace.newBlock('stack_block_1to2'); parent.getInput('NAME').connection .connect(oldChild.previousConnection); const spy = sinon.spy(oldChild.previousConnection, 'onFailedConnect'); parent.getInput('NAME').connection .connect(newChild.previousConnection); chai.assert.isTrue( parent.getInput('NAME').connection.isConnected()); chai.assert.equal( parent.getInputTargetBlock('NAME'), newChild); chai.assert.isFalse(newChild.nextConnection.isConnected()); chai.assert.isTrue(spy.calledOnce); this.assertBlockCount(3); }); test('No end connection', function() { const parent = this.workspace.newBlock('statement_block'); const oldChild = this.workspace.newBlock('stack_block'); const newChild = this.workspace.newBlock('stack_block_noend'); parent.getInput('NAME').connection .connect(oldChild.previousConnection); const spy = sinon.spy(oldChild.previousConnection, 'onFailedConnect'); parent.getInput('NAME').connection .connect(newChild.previousConnection); chai.assert.isTrue( parent.getInput('NAME').connection.isConnected()); chai.assert.equal( parent.getInputTargetBlock('NAME'), newChild); chai.assert.isTrue(spy.calledOnce); this.assertBlockCount(3); }); }); suite('Shadows', function() { test('Simple', function() { const parent = this.workspace.newBlock('statement_block'); const oldChild = this.workspace.newBlock('stack_block'); const newChild = this.workspace.newBlock('stack_block'); parent.getInput('NAME').connection .connect(oldChild.previousConnection); const xml = Blockly.Xml.textToDom( '' ); newChild.nextConnection.setShadowDom(xml); parent.getInput('NAME').connection .connect(newChild.previousConnection); chai.assert.isTrue( parent.getInput('NAME').connection.isConnected()); chai.assert.equal( parent.getInputTargetBlock('NAME'), newChild); chai.assert.isTrue(newChild.nextConnection.isConnected()); chai.assert.equal(newChild.getNextBlock(), oldChild); this.assertBlockCount(3); }); test('Bad check in between', function() { const parent = this.workspace.newBlock('statement_block'); const oldChild = this.workspace.newBlock('stack_block'); const newChild1 = this.workspace.newBlock('stack_block_1to2'); const newChild2 = this.workspace.newBlock('stack_block_2to1'); parent.getInput('NAME').connection .connect(oldChild.previousConnection); newChild1.nextConnection.connect(newChild2.previousConnection); const xml = Blockly.Xml.textToDom( '' ); newChild2.nextConnection.setShadowDom(xml); parent.getInput('NAME').connection .connect(newChild1.previousConnection); chai.assert.isTrue( parent.getInput('NAME').connection.isConnected()); chai.assert.equal( parent.getInputTargetBlock('NAME'), newChild1); chai.assert.isTrue(newChild2.nextConnection.isConnected()); chai.assert.equal(newChild2.getNextBlock(), oldChild); this.assertBlockCount(4); }); test('Bad check at end', function() { const parent = this.workspace.newBlock('statement_block'); const oldChild = this.workspace.newBlock('stack_block'); const newChild = this.workspace.newBlock('stack_block_1to2'); parent.getInput('NAME').connection .connect(oldChild.previousConnection); const xml = Blockly.Xml.textToDom( '' ); newChild.nextConnection.setShadowDom(xml); const spy = sinon.spy(oldChild.previousConnection, 'onFailedConnect'); parent.getInput('NAME').connection .connect(newChild.previousConnection); chai.assert.isTrue( parent.getInput('NAME').connection.isConnected()); chai.assert.equal( parent.getInputTargetBlock('NAME'), newChild); chai.assert.isTrue(newChild.nextConnection.isConnected()); chai.assert.isTrue(newChild.getNextBlock().isShadow()); chai.assert.isTrue(spy.calledOnce); this.assertBlockCount(4); }); }); }); suite('Next', function() { suite('No shadows', function() { test('Simple', function() { const parent = this.workspace.newBlock('stack_block'); const oldChild = this.workspace.newBlock('stack_block'); const newChild = this.workspace.newBlock('stack_block'); parent.nextConnection.connect(oldChild.previousConnection); parent.nextConnection.connect(newChild.previousConnection); chai.assert.isTrue(parent.nextConnection.isConnected()); chai.assert.equal(parent.getNextBlock(), newChild); chai.assert.isTrue(newChild.nextConnection.isConnected()); chai.assert.equal(newChild.getNextBlock(), oldChild); this.assertBlockCount(3); }); test('Bad check in between', function() { const parent = this.workspace.newBlock('stack_block'); const oldChild = this.workspace.newBlock('stack_block'); const newChild1 = this.workspace.newBlock('stack_block_1to2'); const newChild2 = this.workspace.newBlock('stack_block_2to1'); parent.nextConnection.connect(oldChild.previousConnection); newChild1.nextConnection.connect(newChild2.previousConnection); parent.nextConnection.connect(newChild1.previousConnection); chai.assert.isTrue(parent.nextConnection.isConnected()); chai.assert.equal(parent.getNextBlock(), newChild1); chai.assert.isTrue(newChild2.nextConnection.isConnected()); chai.assert.equal(newChild2.getNextBlock(), oldChild); this.assertBlockCount(4); }); test('Bad check at end', function() { const parent = this.workspace.newBlock('stack_block'); const oldChild = this.workspace.newBlock('stack_block'); const newChild = this.workspace.newBlock('stack_block_1to2'); parent.nextConnection.connect(oldChild.previousConnection); const spy = sinon.spy(oldChild.previousConnection, 'onFailedConnect'); parent.nextConnection.connect(newChild.previousConnection); chai.assert.isTrue(parent.nextConnection.isConnected()); chai.assert.equal(parent.getNextBlock(), newChild); chai.assert.isFalse(newChild.nextConnection.isConnected()); chai.assert.isTrue(spy.calledOnce); this.assertBlockCount(3); }); test('No end connection', function() { const parent = this.workspace.newBlock('stack_block'); const oldChild = this.workspace.newBlock('stack_block'); const newChild = this.workspace.newBlock('stack_block_noend'); parent.nextConnection.connect(oldChild.previousConnection); const spy = sinon.spy(oldChild.previousConnection, 'onFailedConnect'); parent.nextConnection.connect(newChild.previousConnection); chai.assert.isTrue(parent.nextConnection.isConnected()); chai.assert.equal(parent.getNextBlock(), newChild); chai.assert.isTrue(spy.calledOnce); this.assertBlockCount(3); }); }); suite('Shadows', function() { test('Simple', function() { const parent = this.workspace.newBlock('stack_block'); const oldChild = this.workspace.newBlock('stack_block'); const newChild = this.workspace.newBlock('stack_block'); parent.nextConnection.connect(oldChild.previousConnection); const xml = Blockly.Xml.textToDom( '' ); newChild.nextConnection.setShadowDom(xml); parent.nextConnection.connect(newChild.previousConnection); chai.assert.isTrue(parent.nextConnection.isConnected()); chai.assert.equal(parent.getNextBlock(), newChild); chai.assert.isTrue(newChild.nextConnection.isConnected()); chai.assert.equal(newChild.getNextBlock(), oldChild); this.assertBlockCount(3); }); test('Bad check in between', function() { const parent = this.workspace.newBlock('stack_block'); const oldChild = this.workspace.newBlock('stack_block'); const newChild1 = this.workspace.newBlock('stack_block_1to2'); const newChild2 = this.workspace.newBlock('stack_block_2to1'); parent.nextConnection.connect(oldChild.previousConnection); newChild1.nextConnection.connect(newChild2.previousConnection); const xml = Blockly.Xml.textToDom( '' ); newChild2.nextConnection.setShadowDom(xml); parent.nextConnection.connect(newChild1.previousConnection); chai.assert.isTrue(parent.nextConnection.isConnected()); chai.assert.equal(parent.getNextBlock(), newChild1); chai.assert.isTrue(newChild2.nextConnection.isConnected()); chai.assert.equal(newChild2.getNextBlock(), oldChild); this.assertBlockCount(4); }); test('Bad check at end', function() { const parent = this.workspace.newBlock('stack_block'); const oldChild = this.workspace.newBlock('stack_block'); const newChild = this.workspace.newBlock('stack_block_1to2'); parent.nextConnection.connect(oldChild.previousConnection); const xml = Blockly.Xml.textToDom( '' ); newChild.nextConnection.setShadowDom(xml); const spy = sinon.spy(oldChild.previousConnection, 'onFailedConnect'); parent.nextConnection.connect(newChild.previousConnection); chai.assert.isTrue(parent.nextConnection.isConnected()); chai.assert.equal(parent.getNextBlock(), newChild); chai.assert.isTrue(newChild.nextConnection.isConnected()); chai.assert.isTrue(newChild.getNextBlock().isShadow()); chai.assert.isTrue(spy.calledOnce); this.assertBlockCount(4); }); }); }); }); }); });