diff --git a/src/engine/blocks.js b/src/engine/blocks.js index 189a595be..94126093d 100644 --- a/src/engine/blocks.js +++ b/src/engine/blocks.js @@ -946,6 +946,32 @@ class Blocks { } } + /** + * Update sensing_of blocks after a variable gets renamed. + * @param {string} oldName The old name of the variable that was renamed. + * @param {string} newName The new name of the variable that was renamed. + * @param {string} targetName The name of the target the variable belongs to. + * @return {boolean} Returns true if any of the blocks were updated. + */ + updateSensingOfReference (oldName, newName, targetName) { + const blocks = this._blocks; + let blockUpdated = false; + for (const blockId in blocks) { + const block = blocks[blockId]; + if (block.opcode === 'sensing_of' && + block.fields.PROPERTY.value === oldName && + // If block and shadow are different, it means a block is inserted to OBJECT, and should be ignored. + block.inputs.OBJECT.block === block.inputs.OBJECT.shadow) { + const inputBlock = this.getBlock(block.inputs.OBJECT.block); + if (inputBlock.fields.OBJECT.value === targetName) { + block.fields.PROPERTY.value = newName; + blockUpdated = true; + } + } + } + return blockUpdated; + } + /** * Helper function to retrieve a costume menu field from a block given its id. * @param {string} blockId A unique identifier for a block diff --git a/src/engine/target.js b/src/engine/target.js index c8bd5094f..9af14990f 100644 --- a/src/engine/target.js +++ b/src/engine/target.js @@ -322,11 +322,26 @@ class Target extends EventEmitter { this.runtime.ioDevices.cloud.requestRenameVariable(oldName, newName); } + if (variable.type === Variable.SCALAR_TYPE) { + // sensing__of may be referencing to this variable. + // Change the reference. + let blockUpdated = false; + this.runtime.targets.forEach(t => { + blockUpdated = t.blocks.updateSensingOfReference( + oldName, + newName, + this.isStage ? '__stage__' : this.getName() + ) || blockUpdated; + }); + // Request workspace change only if sensing_of blocks were actually updated. + if (blockUpdated) this.runtime.requestBlocksUpdate(); + } + const blocks = this.runtime.monitorBlocks; blocks.changeBlock({ id: id, element: 'field', - name: variable.type === 'list' ? 'LIST' : 'VARIABLE', + name: variable.type === Variable.LIST_TYPE ? 'LIST' : 'VARIABLE', value: id }, this.runtime); const monitorBlock = blocks.getBlock(variable.id); diff --git a/test/unit/engine_blocks.js b/test/unit/engine_blocks.js index 7fdaafc73..f4451aa39 100644 --- a/test/unit/engine_blocks.js +++ b/test/unit/engine_blocks.js @@ -26,6 +26,7 @@ test('spec', t => { t.type(b.getBranch, 'function'); t.type(b.getOpcode, 'function'); t.type(b.mutationToXML, 'function'); + t.type(b.updateSensingOfReference, 'function'); t.end(); }); @@ -807,6 +808,146 @@ test('updateAssetName doesn\'t update name if name isn\'t being used', t => { t.end(); }); +test('updateSensingOfReference renames variables in sensing_of block', t => { + const b = new Blocks(new Runtime()); + b.createBlock({ + id: 'id1', + opcode: 'sensing_of', + fields: { + PROPERTY: { + name: 'PROPERTY', + value: 'foo' + } + }, + inputs: { + OBJECT: { + name: 'OBJECT', + block: 'id2', + shadow: 'id2' + } + } + }); + b.createBlock({ + id: 'id2', + fields: { + OBJECT: { + name: 'OBJECT', + value: '_stage_' + } + } + }); + t.equals(b.getBlock('id1').fields.PROPERTY.value, 'foo'); + b.updateSensingOfReference('foo', 'bar', '_stage_'); + t.equals(b.getBlock('id1').fields.PROPERTY.value, 'bar'); + t.end(); +}); + +test('updateSensingOfReference doesn\'t rename if block is inserted', t => { + const b = new Blocks(new Runtime()); + b.createBlock({ + id: 'id1', + opcode: 'sensing_of', + fields: { + PROPERTY: { + name: 'PROPERTY', + value: 'foo' + } + }, + inputs: { + OBJECT: { + name: 'OBJECT', + block: 'id3', + shadow: 'id2' + } + } + }); + b.createBlock({ + id: 'id2', + fields: { + OBJECT: { + name: 'OBJECT', + value: '_stage_' + } + } + }); + b.createBlock({ + id: 'id3', + opcode: 'answer' + }); + t.equals(b.getBlock('id1').fields.PROPERTY.value, 'foo'); + b.updateSensingOfReference('foo', 'bar', '_stage_'); + t.equals(b.getBlock('id1').fields.PROPERTY.value, 'foo'); + t.end(); +}); + +test('updateSensingOfReference doesn\'t rename if name is not being used', t => { + const b = new Blocks(new Runtime()); + b.createBlock({ + id: 'id1', + opcode: 'sensing_of', + fields: { + PROPERTY: { + name: 'PROPERTY', + value: 'foo' + } + }, + inputs: { + OBJECT: { + name: 'OBJECT', + block: 'id2', + shadow: 'id2' + } + } + }); + b.createBlock({ + id: 'id2', + fields: { + OBJECT: { + name: 'OBJECT', + value: '_stage_' + } + } + }); + t.equals(b.getBlock('id1').fields.PROPERTY.value, 'foo'); + b.updateSensingOfReference('meow', 'meow2', '_stage_'); + t.equals(b.getBlock('id1').fields.PROPERTY.value, 'foo'); + t.end(); +}); + +test('updateSensingOfReference doesn\'t rename other targets\' variables', t => { + const b = new Blocks(new Runtime()); + b.createBlock({ + id: 'id1', + opcode: 'sensing_of', + fields: { + PROPERTY: { + name: 'PROPERTY', + value: 'foo' + } + }, + inputs: { + OBJECT: { + name: 'OBJECT', + block: 'id2', + shadow: 'id2' + } + } + }); + b.createBlock({ + id: 'id2', + fields: { + OBJECT: { + name: 'OBJECT', + value: '_stage_' + } + } + }); + t.equals(b.getBlock('id1').fields.PROPERTY.value, 'foo'); + b.updateSensingOfReference('foo', 'bar', 'Cat'); + t.equals(b.getBlock('id1').fields.PROPERTY.value, 'foo'); + t.end(); +}); + test('updateTargetSpecificBlocks changes sprite clicked hat to stage clicked for stage', t => { const b = new Blocks(new Runtime()); b.createBlock({