diff --git a/src/serialization/sb2.js b/src/serialization/sb2.js index e606f5a69..f8d1b9e65 100644 --- a/src/serialization/sb2.js +++ b/src/serialization/sb2.js @@ -227,7 +227,7 @@ const globalBroadcastMsgStateGenerator = (function () { if (name === '') { name = emptyStringName; } - broadcastMsgNameMap[name] = `broadcastMsgId-${name}`; + broadcastMsgNameMap[name] = `broadcastMsgId-${StringUtil.replaceUnsafeChars(name)}`; allBroadcastFields.push(field); return broadcastMsgNameMap[name]; }, diff --git a/test/fixtures/broadcast_special_chars.sb2 b/test/fixtures/broadcast_special_chars.sb2 new file mode 100644 index 000000000..afaf4ba25 Binary files /dev/null and b/test/fixtures/broadcast_special_chars.sb2 differ diff --git a/test/integration/broadcast_special_chars_sb2.js b/test/integration/broadcast_special_chars_sb2.js new file mode 100644 index 000000000..db43dd621 --- /dev/null +++ b/test/integration/broadcast_special_chars_sb2.js @@ -0,0 +1,86 @@ +const path = require('path'); +const test = require('tap').test; +const makeTestStorage = require('../fixtures/make-test-storage'); +const readFileToBuffer = require('../fixtures/readProjectFile').readFileToBuffer; +const VirtualMachine = require('../../src/index'); +const Variable = require('../../src/engine/variable'); +const StringUtil = require('../../src/util/string-util'); +const VariableUtil = require('../../src/util/variable-util'); + +const projectUri = path.resolve(__dirname, '../fixtures/broadcast_special_chars.sb2'); +const project = readFileToBuffer(projectUri); + +test('importing sb2 project with special chars in variable names', t => { + const vm = new VirtualMachine(); + vm.attachStorage(makeTestStorage()); + + // Evaluate playground data and exit + vm.on('playgroundData', e => { + const threads = JSON.parse(e.threads); + // All monitors should create threads that finish during the step and + // are revoved from runtime.threads. + t.equal(threads.length, 0); + + t.equal(vm.runtime.targets.length, 2); + + const stage = vm.runtime.targets[0]; + const cat = vm.runtime.targets[1]; + + const allBroadcastFields = VariableUtil.getAllVarRefsForTargets(vm.runtime.targets, true); + + const abMessageId = Object.keys(stage.variables).filter(k => stage.variables[k].name === 'a&b')[0]; + const abMessage = stage.variables[abMessageId]; + // Check for unsafe characters, replaceUnsafeChars should just result in the original string + // (e.g. there was nothing to replace) + // Check that the variable ID does not have any unsafe characters + t.equal(StringUtil.replaceUnsafeChars(abMessageId), abMessageId); + + // Check that the message still has the correct info + t.equal(StringUtil.replaceUnsafeChars(abMessage.id), abMessage.id); + t.equal(abMessage.id, abMessageId); + t.equal(abMessage.type, Variable.BROADCAST_MESSAGE_TYPE); + t.equal(abMessage.value, 'a&b'); + + + const ltPerfectMessageId = Object.keys(stage.variables).filter(k => stage.variables[k].name === '< perfect')[0]; + const ltPerfectMessage = stage.variables[ltPerfectMessageId]; + // Check for unsafe characters, replaceUnsafeChars should just result in the original string + // (e.g. there was nothing to replace) + // Check that the variable ID does not have any unsafe characters + t.equal(StringUtil.replaceUnsafeChars(ltPerfectMessageId), ltPerfectMessageId); + + // // Check that the message still has the correct info + t.equal(StringUtil.replaceUnsafeChars(ltPerfectMessage.id), ltPerfectMessage.id); + t.equal(ltPerfectMessage.id, ltPerfectMessageId); + t.equal(ltPerfectMessage.type, Variable.BROADCAST_MESSAGE_TYPE); + t.equal(ltPerfectMessage.value, '< perfect'); + + // // Find all the references for this variable, and verify they have the correct ID + // // There should be one + t.equal(allBroadcastFields[ltPerfectMessageId].length, 1); + t.equal(allBroadcastFields[abMessageId].length, 1); + const catBlocks = Object.keys(cat.blocks._blocks).map(blockId => cat.blocks._blocks[blockId]); + const catMessageBlocks = catBlocks.filter(block => block.fields.hasOwnProperty('BROADCAST_OPTION')); + t.equal(catMessageBlocks.length, 2); + t.equal(catMessageBlocks[0].fields.BROADCAST_OPTION.id, ltPerfectMessageId); + t.equal(catMessageBlocks[1].fields.BROADCAST_OPTION.id, abMessageId); + + t.end(); + process.nextTick(process.exit); + }); + + // Start VM, load project, and run + t.doesNotThrow(() => { + vm.start(); + vm.clear(); + vm.setCompatibilityMode(false); + vm.setTurboMode(false); + vm.loadProject(project).then(() => { + vm.greenFlag(); + setTimeout(() => { + vm.getPlaygroundData(); + vm.stopAll(); + }, 100); + }); + }); +});