diff --git a/core/block.js b/core/block.js index 724e76777..6ea9dcc42 100644 --- a/core/block.js +++ b/core/block.js @@ -229,6 +229,17 @@ Blockly.Block = function(workspace, prototypeName, opt_id) { */ Blockly.Block.CommentModel; +/** + * The language-neutral id given to the collapsed input. + * @const {string} + */ +Blockly.Block.COLLAPSED_INPUT_NAME = '_TEMP_COLLAPSED_INPUT'; +/** + * The language-neutral id given to the collapsed field. + * @const {string} + */ +Blockly.Block.COLLAPSED_FIELD_NAME = '_TEMP_COLLAPSED_FIELD'; + /** * Optional text data that round-trips between blocks and XML. * Has no effect. May be used by 3rd parties for meta information. @@ -1295,20 +1306,19 @@ Blockly.Block.prototype.setCollapsed = function(collapsed) { Blockly.Block.prototype.toString = function(opt_maxLength, opt_emptyToken) { var text = []; var emptyFieldPlaceholder = opt_emptyToken || '?'; - if (this.collapsed_) { - text.push(this.getInput('_TEMP_COLLAPSED_INPUT').fieldRow[0].getText()); - } else { - for (var i = 0, input; (input = this.inputList[i]); i++) { - for (var j = 0, field; (field = input.fieldRow[j]); j++) { - text.push(field.getText()); - } - if (input.connection) { - var child = input.connection.targetBlock(); - if (child) { - text.push(child.toString(undefined, opt_emptyToken)); - } else { - text.push(emptyFieldPlaceholder); - } + for (var i = 0, input; (input = this.inputList[i]); i++) { + if (input.name == Blockly.Block.COLLAPSED_INPUT_NAME) { + continue; + } + for (var j = 0, field; (field = input.fieldRow[j]); j++) { + text.push(field.getText()); + } + if (input.connection) { + var child = input.connection.targetBlock(); + if (child) { + text.push(child.toString(undefined, opt_emptyToken)); + } else { + text.push(emptyFieldPlaceholder); } } } diff --git a/core/block_svg.js b/core/block_svg.js index bebf725b3..5938e597e 100644 --- a/core/block_svg.js +++ b/core/block_svg.js @@ -69,6 +69,14 @@ Blockly.BlockSvg = function(workspace, prototypeName, opt_id) { /** @type {boolean} */ this.rendered = false; + /** + * Is this block currently rendering? Used to stop recursive render calls + * from actually triggering a re-render. + * @type {boolean} + * @private + */ + this.renderIsInProgress_ = false; + /** @type {!Blockly.WorkspaceSvg} */ this.workspace = workspace; @@ -602,60 +610,51 @@ Blockly.BlockSvg.prototype.setCollapsed = function(collapsed) { if (this.collapsed_ == collapsed) { return; } - var renderList = []; - // Show/hide the inputs. - for (var i = 0, input; (input = this.inputList[i]); i++) { - renderList.push.apply(renderList, input.setVisible(!collapsed)); - } - - var COLLAPSED_INPUT_NAME = '_TEMP_COLLAPSED_INPUT'; - if (collapsed) { - var icons = this.getIcons(); - for (var i = 0; i < icons.length; i++) { - icons[i].setVisible(false); - } - var text = this.toString(Blockly.COLLAPSE_CHARS); - this.appendDummyInput(COLLAPSED_INPUT_NAME).appendField(text).init(); - - // Add any warnings on enclosed blocks to this block. - var descendants = this.getDescendants(true); - var nextBlock = this.getNextBlock(); - if (nextBlock) { - var index = descendants.indexOf(nextBlock); - descendants.splice(index, descendants.length - index); - } - for (var i = 1, block; (block = descendants[i]); i++) { - if (block.warning) { - this.setWarningText(Blockly.Msg['COLLAPSED_WARNINGS_WARNING'], - Blockly.BlockSvg.COLLAPSED_WARNING_ID); - break; - } - } - } else { - this.removeInput(COLLAPSED_INPUT_NAME); - // Clear any warnings inherited from enclosed blocks. - if (this.warning) { - this.warning.setText('', Blockly.BlockSvg.COLLAPSED_WARNING_ID); - if (!Object.keys(this.warning.text_).length) { - this.setWarningText(null); - } - } - } Blockly.BlockSvg.superClass_.setCollapsed.call(this, collapsed); + if (!collapsed) { + this.updateCollapsed_(); + } else if (this.rendered) { + this.render(); + // Don't bump neighbours. Users like to store collapsed functions together + // and bumping makes them go out of alignment. + } +}; - if (!renderList.length) { - // No child blocks, just render this block. - renderList[0] = this; - } - if (this.rendered) { - for (var i = 0, block; (block = renderList[i]); i++) { - block.render(); +/** + * Makes sure that when the block is collapsed, it is rendered correctly + * for that state. + * @private + */ +Blockly.BlockSvg.prototype.updateCollapsed_ = function() { + var collapsed = this.isCollapsed(); + var collapsedInputName = Blockly.Block.COLLAPSED_INPUT_NAME; + var collapsedFieldName = Blockly.Block.COLLAPSED_FIELD_NAME; + + for (var i = 0, input; (input = this.inputList[i]); i++) { + if (input.name != collapsedInputName) { + input.setVisible(!collapsed); } - // Don't bump neighbours. - // Although bumping neighbours would make sense, users often collapse - // all their functions and store them next to each other. Expanding and - // bumping causes all their definitions to go out of alignment. } + + if (!collapsed) { + this.removeInput(collapsedInputName); + return; + } + + var icons = this.getIcons(); + for (var i = 0, icon; (icon = icons[i]); i++) { + icon.setVisible(false); + } + + var text = this.toString(Blockly.COLLAPSE_CHARS); + var field = this.getField(collapsedFieldName); + if (field) { + field.setValue(text); + return; + } + var input = this.getInput(collapsedInputName) || + this.appendDummyInput(collapsedInputName); + input.appendField(new Blockly.FieldLabel(text), collapsedFieldName); }; /** @@ -1647,31 +1646,40 @@ Blockly.BlockSvg.prototype.getRootBlock = function() { }; /** - * Render the block. * Lays out and reflows a block based on its contents and settings. * @param {boolean=} opt_bubble If false, just render this block. * If true, also render block's parent, grandparent, etc. Defaults to true. */ Blockly.BlockSvg.prototype.render = function(opt_bubble) { - Blockly.utils.dom.startTextWidthCache(); - this.rendered = true; - (/** @type {!Blockly.WorkspaceSvg} */ (this.workspace)) - .getRenderer().render(this); - // No matter how we rendered, connection locations should now be correct. - this.updateConnectionLocations_(); - if (opt_bubble !== false) { - // Render all blocks above this one (propagate a reflow). - var parentBlock = this.getParent(); - if (parentBlock) { - parentBlock.render(true); - } else { - // Top-most block. Fire an event to allow scrollbars to resize. - this.workspace.resizeContents(); - } + if (this.renderIsInProgress_) { + return; // Don't allow recursive renders. } - Blockly.utils.dom.stopTextWidthCache(); + this.renderIsInProgress_ = true; + try { + this.rendered = true; + Blockly.utils.dom.startTextWidthCache(); - this.updateMarkers_(); + if (this.isCollapsed()) { + this.updateCollapsed_(); + } + this.workspace.getRenderer().render(this); + this.updateConnectionLocations_(); + + if (opt_bubble !== false) { + var parentBlock = this.getParent(); + if (parentBlock) { + parentBlock.render(true); + } else { + // Top-most block. Fire an event to allow scrollbars to resize. + this.workspace.resizeContents(); + } + } + + Blockly.utils.dom.stopTextWidthCache(); + this.updateMarkers_(); + } finally { + this.renderIsInProgress_ = false; + } }; /** diff --git a/core/input.js b/core/input.js index 8e6bfb7b4..aeb60a9de 100644 --- a/core/input.js +++ b/core/input.js @@ -91,21 +91,23 @@ Blockly.Input.prototype.insertFieldAt = function(index, field, opt_name) { if (index < 0 || index > this.fieldRow.length) { throw Error('index ' + index + ' out of bounds.'); } - // Falsy field values don't generate a field, unless the field is an empty // string and named. if (!field && !(field == '' && opt_name)) { return index; } + // Generate a FieldLabel when given a plain text field. if (typeof field == 'string') { field = new Blockly.FieldLabel(/** @type {string} */ (field)); } + field.setSourceBlock(this.sourceBlock_); if (this.sourceBlock_.rendered) { field.init(); } field.name = opt_name; + field.setVisible(this.isVisible()); if (field.prefixField) { // Add any prefix. @@ -173,7 +175,6 @@ Blockly.Input.prototype.setVisible = function(visible) { } this.visible_ = visible; - var display = visible ? 'block' : 'none'; for (var y = 0, field; (field = this.fieldRow[y]); y++) { field.setVisible(visible); } @@ -186,10 +187,7 @@ Blockly.Input.prototype.setVisible = function(visible) { } var child = this.connection.targetBlock(); if (child) { - child.getSvgRoot().style.display = display; - if (!visible) { - child.rendered = false; - } + child.getSvgRoot().style.display = visible ? 'block' : 'none'; } } return renderList; diff --git a/core/insertion_marker_manager.js b/core/insertion_marker_manager.js index 49f264e72..bb85cc960 100644 --- a/core/insertion_marker_manager.js +++ b/core/insertion_marker_manager.js @@ -253,23 +253,25 @@ Blockly.InsertionMarkerManager.prototype.createMarkerBlock_ = function(sourceBlo result.domToMutation(oldMutationDom); } } - result.setCollapsed(sourceBlock.isCollapsed()); - result.setInputsInline(sourceBlock.getInputsInline()); - // Copy visible field values from the other block. These values may impact - // the rendered size of the insertion marker. Note that we do not care - // about child blocks here. + // Copy field values from the other block. These values may impact the + // rendered size of the insertion marker. Note that we do not care about + // child blocks here. for (var i = 0; i < sourceBlock.inputList.length; i++) { var sourceInput = sourceBlock.inputList[i]; - if (sourceInput.isVisible()) { - var resultInput = result.inputList[i]; - for (var j = 0; j < sourceInput.fieldRow.length; j++) { - var sourceField = sourceInput.fieldRow[j]; - var resultField = resultInput.fieldRow[j]; - resultField.setValue(sourceField.getValue()); - } + if (sourceInput.name == Blockly.Block.COLLAPSED_INPUT_NAME) { + continue; // Ignore the collapsed input. + } + var resultInput = result.inputList[i]; + for (var j = 0; j < sourceInput.fieldRow.length; j++) { + var sourceField = sourceInput.fieldRow[j]; + var resultField = resultInput.fieldRow[j]; + resultField.setValue(sourceField.getValue()); } } + result.setCollapsed(sourceBlock.isCollapsed()); + result.setInputsInline(sourceBlock.getInputsInline()); + result.initSvg(); result.getSvgRoot().setAttribute('visibility', 'hidden'); } finally { diff --git a/core/rendered_connection.js b/core/rendered_connection.js index d6eab4827..fa6c45643 100644 --- a/core/rendered_connection.js +++ b/core/rendered_connection.js @@ -454,6 +454,8 @@ Blockly.RenderedConnection.prototype.disconnectInternal_ = function(parentBlock, if (childBlock.rendered) { childBlock.updateDisabled(); childBlock.render(); + // Reset visibility, since the child is now a top block. + childBlock.getSvgRoot().style.display = 'block'; } }; @@ -504,14 +506,16 @@ Blockly.RenderedConnection.prototype.connect_ = function(childConnection) { var parentConnection = this; var parentBlock = parentConnection.getSourceBlock(); var childBlock = childConnection.getSourceBlock(); + var parentRendered = parentBlock.rendered; + var childRendered = childBlock.rendered; - if (parentBlock.rendered) { + if (parentRendered) { parentBlock.updateDisabled(); } - if (childBlock.rendered) { + if (childRendered) { childBlock.updateDisabled(); } - if (parentBlock.rendered && childBlock.rendered) { + if (parentRendered && childRendered) { if (parentConnection.type == Blockly.NEXT_STATEMENT || parentConnection.type == Blockly.PREVIOUS_STATEMENT) { // Child block may need to square off its corners if it is in a stack. @@ -523,6 +527,13 @@ Blockly.RenderedConnection.prototype.connect_ = function(childConnection) { parentBlock.render(); } } + + // The input the child block is connected to (if any). + var parentInput = parentBlock.getInputWithBlock(childBlock); + if (parentInput) { + var visible = parentInput.isVisible(); + childBlock.getSvgRoot().style.display = visible ? 'block' : 'none'; + } }; /** diff --git a/tests/mocha/block_test.js b/tests/mocha/block_test.js index dcdea6a61..f29b767db 100644 --- a/tests/mocha/block_test.js +++ b/tests/mocha/block_test.js @@ -1209,7 +1209,434 @@ suite('Blocks', function() { }); }); }); + suite('Collapsing and Expanding', function() { + function assertCollapsed(block, opt_string) { + chai.assert.isTrue(block.isCollapsed()); + for (var i = 0, input; (input = block.inputList[i]); i++) { + if (input.name == Blockly.Block.COLLAPSED_INPUT_NAME) { + continue; + } + chai.assert.isFalse(input.isVisible()); + for (var j = 0, field; (field = input.fieldRow[j]); j++) { + chai.assert.isFalse(field.isVisible()); + } + } + var icons = block.getIcons(); + for (var i = 0, icon; (icon = icons[i]); i++) { + chai.assert.isFalse(icon.isVisible()); + } + var input = block.getInput(Blockly.Block.COLLAPSED_INPUT_NAME); + chai.assert.isNotNull(input); + chai.assert.isTrue(input.isVisible()); + var field = block.getField(Blockly.Block.COLLAPSED_FIELD_NAME); + chai.assert.isNotNull(field); + chai.assert.isTrue(field.isVisible()); + + if (opt_string) { + chai.assert.equal(field.getText(), opt_string); + } + } + function assertNotCollapsed(block) { + chai.assert.isFalse(block.isCollapsed()); + for (var i = 0, input; (input = block.inputList[i]); i++) { + chai.assert.isTrue(input.isVisible()); + for (var j = 0, field; (field = input.fieldRow[j]); j++) { + chai.assert.isTrue(field.isVisible()); + } + } + + var input = block.getInput(Blockly.Block.COLLAPSED_INPUT_NAME); + chai.assert.isNull(input); + var field = block.getField(Blockly.Block.COLLAPSED_FIELD_NAME); + chai.assert.isNull(field); + } + function isBlockHidden(block) { + var node = block.getSvgRoot(); + do { + var visible = node.style.display != 'none'; + if (!visible) { + return true; + } + node = node.parentNode; + } while (node != document); + return false; + } + + setup(function() { + Blockly.Events.disable(); + // We need a visible workspace. + this.workspace = Blockly.inject('blocklyDiv', {}); + Blockly.defineBlocksWithJsonArray([ + { + "type": "variable_block", + "message0": "%1", + "args0": [ + { + "type": "field_variable", + "name": "NAME", + "variable": "x" + } + ], + } + ]); + this.createBlock = function(type) { + var block = this.workspace.newBlock(type); + block.initSvg(); + block.render(); + return block; + }; + }); + teardown(function() { + Blockly.Events.enable(); + delete Blockly.Blocks['variable_block']; + }); + suite('Connecting and Disconnecting', function() { + test('Connect Block to Next', function() { + var blockA = this.createBlock('stack_block'); + var blockB = this.createBlock('stack_block'); + + blockA.setCollapsed(true); + assertCollapsed(blockA); + blockA.nextConnection.connect(blockB.previousConnection); + assertNotCollapsed(blockB); + }); + test('Connect Block to Value Input', function() { + var blockA = this.createBlock('row_block'); + var blockB = this.createBlock('row_block'); + + blockA.setCollapsed(true); + assertCollapsed(blockA); + blockA.getInput('INPUT').connection.connect(blockB.outputConnection); + chai.assert.isTrue(isBlockHidden(blockB)); + blockA.setCollapsed(false); + assertNotCollapsed(blockA); + chai.assert.isFalse(isBlockHidden(blockB)); + }); + test('Connect Block to Statement Input', function() { + var blockA = this.createBlock('statement_block'); + var blockB = this.createBlock('stack_block'); + + blockA.setCollapsed(true); + assertCollapsed(blockA); + blockA.getInput('STATEMENT').connection + .connect(blockB.previousConnection); + chai.assert.isTrue(isBlockHidden(blockB)); + blockA.setCollapsed(false); + assertNotCollapsed(blockA); + chai.assert.isFalse(isBlockHidden(blockB)); + }); + test('Connect Block to Child of Collapsed - Input', function() { + var blockA = this.createBlock('row_block'); + var blockB = this.createBlock('row_block'); + var blockC = this.createBlock('row_block'); + + blockA.getInput('INPUT').connection.connect(blockB.outputConnection); + blockA.setCollapsed(true); + assertCollapsed(blockA); + chai.assert.isTrue(isBlockHidden(blockB)); + blockB.getInput('INPUT').connection.connect(blockC.outputConnection); + chai.assert.isTrue(isBlockHidden(blockC)); + + blockA.setCollapsed(false); + assertNotCollapsed(blockA); + chai.assert.isFalse(isBlockHidden(blockB)); + chai.assert.isFalse(isBlockHidden(blockC)); + }); + test('Connect Block to Child of Collapsed - Next', function() { + var blockA = this.createBlock('statement_block'); + var blockB = this.createBlock('stack_block'); + var blockC = this.createBlock('stack_block'); + + blockA.getInput('STATEMENT').connection + .connect(blockB.previousConnection); + blockA.setCollapsed(true); + assertCollapsed(blockA); + chai.assert.isTrue(isBlockHidden(blockB)); + blockB.nextConnection.connect(blockC.previousConnection); + chai.assert.isTrue(isBlockHidden(blockC)); + + blockA.setCollapsed(false); + assertNotCollapsed(blockA); + chai.assert.isFalse(isBlockHidden(blockB)); + chai.assert.isFalse(isBlockHidden(blockC)); + }); + test('Connect Block to Value Input Already Taken', function() { + var blockA = this.createBlock('row_block'); + var blockB = this.createBlock('row_block'); + var blockC = this.createBlock('row_block'); + + blockA.getInput('INPUT').connection.connect(blockB.outputConnection); + blockA.setCollapsed(true); + assertCollapsed(blockA); + chai.assert.isTrue(isBlockHidden(blockB)); + blockA.getInput('INPUT').connection.connect(blockC.outputConnection); + chai.assert.isTrue(isBlockHidden(blockC)); + // Still hidden after C is inserted between. + chai.assert.isTrue(isBlockHidden(blockB)); + + blockA.setCollapsed(false); + assertNotCollapsed(blockA); + chai.assert.isFalse(isBlockHidden(blockB)); + chai.assert.isFalse(isBlockHidden(blockC)); + }); + test('Connect Block to Statement Input Already Taken', function() { + var blockA = this.createBlock('statement_block'); + var blockB = this.createBlock('stack_block'); + var blockC = this.createBlock('stack_block'); + + blockA.getInput('STATEMENT').connection + .connect(blockB.previousConnection); + blockA.setCollapsed(true); + assertCollapsed(blockA); + chai.assert.isTrue(isBlockHidden(blockB)); + blockA.getInput('STATEMENT').connection + .connect(blockC.previousConnection); + chai.assert.isTrue(isBlockHidden(blockC)); + // Still hidden after C is inserted between. + chai.assert.isTrue(isBlockHidden(blockB)); + + blockA.setCollapsed(false); + assertNotCollapsed(blockA); + chai.assert.isFalse(isBlockHidden(blockB)); + chai.assert.isFalse(isBlockHidden(blockC)); + }); + test('Connect Block with Child - Input', function() { + var blockA = this.createBlock('row_block'); + var blockB = this.createBlock('row_block'); + var blockC = this.createBlock('row_block'); + + blockB.getInput('INPUT').connection.connect(blockC.outputConnection); + blockA.setCollapsed(true); + assertCollapsed(blockA); + blockA.getInput('INPUT').connection.connect(blockB.outputConnection); + chai.assert.isTrue(isBlockHidden(blockC)); + chai.assert.isTrue(isBlockHidden(blockB)); + + blockA.setCollapsed(false); + assertNotCollapsed(blockA); + chai.assert.isFalse(isBlockHidden(blockB)); + chai.assert.isFalse(isBlockHidden(blockC)); + }); + test('Connect Block with Child - Statement', function() { + var blockA = this.createBlock('statement_block'); + var blockB = this.createBlock('stack_block'); + var blockC = this.createBlock('stack_block'); + + blockB.nextConnection.connect(blockC.previousConnection); + blockA.setCollapsed(true); + assertCollapsed(blockA); + blockA.getInput('STATEMENT').connection + .connect(blockB.previousConnection); + chai.assert.isTrue(isBlockHidden(blockC)); + chai.assert.isTrue(isBlockHidden(blockB)); + + blockA.setCollapsed(false); + assertNotCollapsed(blockA); + chai.assert.isFalse(isBlockHidden(blockB)); + chai.assert.isFalse(isBlockHidden(blockC)); + }); + test('Disconnect Block from Value Input', function() { + var blockA = this.createBlock('row_block'); + var blockB = this.createBlock('row_block'); + + blockA.getInput('INPUT').connection.connect(blockB.outputConnection); + blockA.setCollapsed(true); + assertCollapsed(blockA); + chai.assert.isTrue(isBlockHidden(blockB)); + blockB.outputConnection.disconnect(); + chai.assert.isFalse(isBlockHidden(blockB)); + }); + test('Disconnect Block from Statement Input', function() { + var blockA = this.createBlock('statement_block'); + var blockB = this.createBlock('stack_block'); + + blockA.getInput('STATEMENT').connection + .connect(blockB.previousConnection); + blockA.setCollapsed(true); + assertCollapsed(blockA); + chai.assert.isTrue(isBlockHidden(blockB)); + blockB.previousConnection.disconnect(); + chai.assert.isFalse(isBlockHidden(blockB)); + }); + test('Disconnect Block from Child of Collapsed - Input', function() { + var blockA = this.createBlock('row_block'); + var blockB = this.createBlock('row_block'); + var blockC = this.createBlock('row_block'); + + blockA.getInput('INPUT').connection.connect(blockB.outputConnection); + blockB.getInput('INPUT').connection.connect(blockC.outputConnection); + blockA.setCollapsed(true); + assertCollapsed(blockA); + chai.assert.isTrue(isBlockHidden(blockB)); + chai.assert.isTrue(isBlockHidden(blockC)); + + blockC.outputConnection.disconnect(); + chai.assert.isFalse(isBlockHidden(blockC)); + }); + test('Disconnect Block from Child of Collapsed - Next', function() { + var blockA = this.createBlock('statement_block'); + var blockB = this.createBlock('stack_block'); + var blockC = this.createBlock('stack_block'); + + blockA.getInput('STATEMENT').connection + .connect(blockB.previousConnection); + blockB.nextConnection.connect(blockC.previousConnection); + blockA.setCollapsed(true); + assertCollapsed(blockA); + chai.assert.isTrue(isBlockHidden(blockB)); + chai.assert.isTrue(isBlockHidden(blockC)); + + blockC.previousConnection.disconnect(); + chai.assert.isFalse(isBlockHidden(blockC)); + }); + test('Disconnect Block with Child - Input', function() { + var blockA = this.createBlock('row_block'); + var blockB = this.createBlock('row_block'); + var blockC = this.createBlock('row_block'); + + blockB.getInput('INPUT').connection.connect(blockC.outputConnection); + blockA.getInput('INPUT').connection.connect(blockB.outputConnection); + blockA.setCollapsed(true); + assertCollapsed(blockA); + chai.assert.isTrue(isBlockHidden(blockB)); + chai.assert.isTrue(isBlockHidden(blockC)); + + blockB.outputConnection.disconnect(); + chai.assert.isFalse(isBlockHidden(blockB)); + chai.assert.isFalse(isBlockHidden(blockC)); + }); + test('Disconnect Block with Child - Statement', function() { + var blockA = this.createBlock('statement_block'); + var blockB = this.createBlock('stack_block'); + var blockC = this.createBlock('stack_block'); + + blockB.nextConnection.connect(blockC.previousConnection); + blockA.getInput('STATEMENT').connection + .connect(blockB.previousConnection); + blockA.setCollapsed(true); + assertCollapsed(blockA); + chai.assert.isTrue(isBlockHidden(blockC)); + chai.assert.isTrue(isBlockHidden(blockB)); + + blockB.previousConnection.disconnect(); + chai.assert.isFalse(isBlockHidden(blockB)); + chai.assert.isFalse(isBlockHidden(blockC)); + }); + }); + suite('Adding and Removing Block Parts', function() { + test('Add Previous Connection', function() { + var blockA = this.createBlock('empty_block'); + blockA.setCollapsed(true); + assertCollapsed(blockA); + blockA.setPreviousStatement(true); + assertCollapsed(blockA); + chai.assert.isNotNull(blockA.previousConnection); + }); + test('Add Next Connection', function() { + var blockA = this.createBlock('empty_block'); + blockA.setCollapsed(true); + assertCollapsed(blockA); + blockA.setNextStatement(true); + assertCollapsed(blockA); + chai.assert.isNotNull(blockA.nextConnection); + }); + test('Add Input', function() { + var blockA = this.createBlock('empty_block'); + blockA.setCollapsed(true); + assertCollapsed(blockA); + blockA.appendDummyInput('NAME'); + assertCollapsed(blockA); + chai.assert.isNotNull(blockA.getInput('NAME')); + }); + test('Add Field', function() { + var blockA = this.createBlock('empty_block'); + var input = blockA.appendDummyInput('NAME'); + blockA.setCollapsed(true); + assertCollapsed(blockA); + input.appendField(new Blockly.FieldLabel('test'), 'FIELD'); + assertCollapsed(blockA); + var field = blockA.getField('FIELD'); + chai.assert.isNotNull(field); + chai.assert.equal('test', field.getText()); + }); + test('Add Icon', function() { + var blockA = this.createBlock('empty_block'); + blockA.setCollapsed(true); + assertCollapsed(blockA); + blockA.setCommentText('test'); + assertCollapsed(blockA); + }); + test('Remove Previous Connection', function() { + var blockA = this.createBlock('empty_block'); + blockA.setPreviousStatement(true); + blockA.setCollapsed(true); + assertCollapsed(blockA); + blockA.setPreviousStatement(false); + assertCollapsed(blockA); + chai.assert.isNull(blockA.previousConnection); + }); + test('Remove Next Connection', function() { + var blockA = this.createBlock('empty_block'); + blockA.setNextStatement(true); + blockA.setCollapsed(true); + assertCollapsed(blockA); + blockA.setNextStatement(false); + assertCollapsed(blockA); + chai.assert.isNull(blockA.nextConnection); + }); + test('Remove Input', function() { + var blockA = this.createBlock('empty_block'); + blockA.appendDummyInput('NAME'); + blockA.setCollapsed(true); + assertCollapsed(blockA); + blockA.removeInput('NAME'); + assertCollapsed(blockA); + chai.assert.isNull(blockA.getInput('NAME')); + }); + test('Remove Field', function() { + var blockA = this.createBlock('empty_block'); + var input = blockA.appendDummyInput('NAME'); + input.appendField(new Blockly.FieldLabel('test'), 'FIELD'); + blockA.setCollapsed(true); + assertCollapsed(blockA); + input.removeField('FIELD'); + assertCollapsed(blockA); + var field = blockA.getField('FIELD'); + chai.assert.isNull(field); + }); + test('Remove Icon', function() { + var blockA = this.createBlock('empty_block'); + blockA.setCommentText('test'); + blockA.setCollapsed(true); + assertCollapsed(blockA); + blockA.setCommentText(null); + assertCollapsed(blockA); + }); + }); + suite('Renaming Vars', function() { + test('Simple Rename', function() { + var blockA = this.createBlock('variable_block'); + + blockA.setCollapsed(true); + assertCollapsed(blockA, 'x'); + + var variable = this.workspace.getVariable('x', ''); + this.workspace.renameVariableById(variable.getId(), 'y'); + assertCollapsed(blockA, 'y'); + }); + test('Coalesce, Different Case', function() { + var blockA = this.createBlock('variable_block'); + + blockA.setCollapsed(true); + assertCollapsed(blockA, 'x'); + + var variable = this.workspace.createVariable('y'); + this.workspace.renameVariableById(variable.getId(), 'X'); + assertCollapsed(blockA, 'X'); + }); + }); + }); suite('Style', function() { suite('Headless', function() { setup(function() { diff --git a/tests/mocha/run_mocha_tests_in_browser.js b/tests/mocha/run_mocha_tests_in_browser.js index ed0c82943..768e8a7d7 100644 --- a/tests/mocha/run_mocha_tests_in_browser.js +++ b/tests/mocha/run_mocha_tests_in_browser.js @@ -40,7 +40,7 @@ async function runMochaTestsInBrowser() { var elem = await browser.$('#failureCount'); var text = await elem.getAttribute('tests_failed'); return text != 'unset'; - }, 7000); + }, 9000); const elem = await browser.$('#failureCount'); const numOfFailure = await elem.getAttribute('tests_failed');