mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-23 14:32:59 -05:00
Merge pull request #1983 from picklesrus/broadcast-msg-chars
Broadcast msg chars
This commit is contained in:
commit
765f2b2549
11 changed files with 215 additions and 6 deletions
|
@ -823,11 +823,12 @@ class Blocks {
|
||||||
* @param {Array<object>} optBlocks Optional list of blocks to constrain the search to.
|
* @param {Array<object>} optBlocks Optional list of blocks to constrain the search to.
|
||||||
* This is useful for getting variable/list references for a stack of blocks instead
|
* This is useful for getting variable/list references for a stack of blocks instead
|
||||||
* of all blocks on the workspace
|
* of all blocks on the workspace
|
||||||
|
* @param {?boolean} optIncludeBroadcast Optional whether to include broadcast fields.
|
||||||
* @return {object} A map of variable ID to a list of all variable references
|
* @return {object} A map of variable ID to a list of all variable references
|
||||||
* for that ID. A variable reference contains the field referencing that variable
|
* for that ID. A variable reference contains the field referencing that variable
|
||||||
* and also the type of the variable being referenced.
|
* and also the type of the variable being referenced.
|
||||||
*/
|
*/
|
||||||
getAllVariableAndListReferences (optBlocks) {
|
getAllVariableAndListReferences (optBlocks, optIncludeBroadcast) {
|
||||||
const blocks = optBlocks ? optBlocks : this._blocks;
|
const blocks = optBlocks ? optBlocks : this._blocks;
|
||||||
const allReferences = Object.create(null);
|
const allReferences = Object.create(null);
|
||||||
for (const blockId in blocks) {
|
for (const blockId in blocks) {
|
||||||
|
@ -839,6 +840,9 @@ class Blocks {
|
||||||
} else if (blocks[blockId].fields.LIST) {
|
} else if (blocks[blockId].fields.LIST) {
|
||||||
varOrListField = blocks[blockId].fields.LIST;
|
varOrListField = blocks[blockId].fields.LIST;
|
||||||
varType = Variable.LIST_TYPE;
|
varType = Variable.LIST_TYPE;
|
||||||
|
} else if (optIncludeBroadcast && blocks[blockId].fields.BROADCAST_OPTION) {
|
||||||
|
varOrListField = blocks[blockId].fields.BROADCAST_OPTION;
|
||||||
|
varType = Variable.BROADCAST_MESSAGE_TYPE;
|
||||||
}
|
}
|
||||||
if (varOrListField) {
|
if (varOrListField) {
|
||||||
const currVarId = varOrListField.id;
|
const currVarId = varOrListField.id;
|
||||||
|
|
|
@ -227,7 +227,7 @@ const globalBroadcastMsgStateGenerator = (function () {
|
||||||
if (name === '') {
|
if (name === '') {
|
||||||
name = emptyStringName;
|
name = emptyStringName;
|
||||||
}
|
}
|
||||||
broadcastMsgNameMap[name] = `broadcastMsgId-${name}`;
|
broadcastMsgNameMap[name] = `broadcastMsgId-${StringUtil.replaceUnsafeChars(name)}`;
|
||||||
allBroadcastFields.push(field);
|
allBroadcastFields.push(field);
|
||||||
return broadcastMsgNameMap[name];
|
return broadcastMsgNameMap[name];
|
||||||
},
|
},
|
||||||
|
|
|
@ -1141,7 +1141,7 @@ const deserializeMonitor = function (monitorData, runtime, targets, extensions)
|
||||||
// This is to fix up projects imported from 2.0 where xml-unsafe names
|
// This is to fix up projects imported from 2.0 where xml-unsafe names
|
||||||
// were getting added to the variable ids.
|
// were getting added to the variable ids.
|
||||||
const replaceUnsafeCharsInVariableIds = function (targets) {
|
const replaceUnsafeCharsInVariableIds = function (targets) {
|
||||||
const allVarRefs = VariableUtil.getAllVarRefsForTargets(targets);
|
const allVarRefs = VariableUtil.getAllVarRefsForTargets(targets, true);
|
||||||
// Re-id the variables in the actual targets
|
// Re-id the variables in the actual targets
|
||||||
targets.forEach(t => {
|
targets.forEach(t => {
|
||||||
Object.keys(t.variables).forEach(id => {
|
Object.keys(t.variables).forEach(id => {
|
||||||
|
|
|
@ -15,12 +15,13 @@ class VariableUtil {
|
||||||
* in the project.
|
* in the project.
|
||||||
* @param {Array.<Target>} targets The list of targets to get the variable
|
* @param {Array.<Target>} targets The list of targets to get the variable
|
||||||
* and list references from.
|
* and list references from.
|
||||||
|
* @param {boolean} shouldIncludeBroadcast Whether to include broadcast message fields.
|
||||||
* @return {object} An object with variable ids as the keys and a list of block fields referencing
|
* @return {object} An object with variable ids as the keys and a list of block fields referencing
|
||||||
* the variable.
|
* the variable.
|
||||||
*/
|
*/
|
||||||
static getAllVarRefsForTargets (targets) {
|
static getAllVarRefsForTargets (targets, shouldIncludeBroadcast) {
|
||||||
return targets
|
return targets
|
||||||
.map(t => t.blocks.getAllVariableAndListReferences())
|
.map(t => t.blocks.getAllVariableAndListReferences(null, shouldIncludeBroadcast))
|
||||||
.reduce(VariableUtil._mergeVarRefObjects, {});
|
.reduce(VariableUtil._mergeVarRefObjects, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
BIN
test/fixtures/broadcast_special_chars.sb2
vendored
Normal file
BIN
test/fixtures/broadcast_special_chars.sb2
vendored
Normal file
Binary file not shown.
BIN
test/fixtures/broadcast_special_chars.sb3
vendored
Normal file
BIN
test/fixtures/broadcast_special_chars.sb3
vendored
Normal file
Binary file not shown.
6
test/fixtures/events.json
vendored
6
test/fixtures/events.json
vendored
|
@ -97,6 +97,12 @@
|
||||||
"outerHTML": "<block type='data_variable' id='a block' x='0' y='0'><field name='VARIABLE' id='mock var id' variabletype=''>a mock variable</field></block>"
|
"outerHTML": "<block type='data_variable' id='a block' x='0' y='0'><field name='VARIABLE' id='mock var id' variabletype=''>a mock variable</field></block>"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"mockBroadcastBlock": {
|
||||||
|
"name": "block",
|
||||||
|
"xml": {
|
||||||
|
"outerHTML": "<block type='event_broadcast' id='a broadcast block' x='0' y='0'><value name='BROADCAST_INPUT' id='mock broadcast input'><shadow id='boadcast shadow' type='event_broadcast_menu'><field name='BROADCAST_OPTION' id='mock broadcast message id' variabletype='broadcast-msg'>my message</field></shadow></value></block>"
|
||||||
|
}
|
||||||
|
},
|
||||||
"mockListBlock": {
|
"mockListBlock": {
|
||||||
"name": "block",
|
"name": "block",
|
||||||
"xml": {
|
"xml": {
|
||||||
|
|
83
test/integration/broadcast_special_chars_sb2.js
Normal file
83
test/integration/broadcast_special_chars_sb2.js
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
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 message names', t => {
|
||||||
|
const vm = new VirtualMachine();
|
||||||
|
vm.attachStorage(makeTestStorage());
|
||||||
|
|
||||||
|
// Evaluate playground data and exit
|
||||||
|
vm.on('playgroundData', e => {
|
||||||
|
const threads = JSON.parse(e.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 message 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 message 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 these messages, and verify they have the correct ID
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
83
test/integration/broadcast_special_chars_sb3.js
Normal file
83
test/integration/broadcast_special_chars_sb3.js
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
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.sb3');
|
||||||
|
const project = readFileToBuffer(projectUri);
|
||||||
|
|
||||||
|
test('importing sb3 project with special chars in message names', t => {
|
||||||
|
const vm = new VirtualMachine();
|
||||||
|
vm.attachStorage(makeTestStorage());
|
||||||
|
|
||||||
|
// Evaluate playground data and exit
|
||||||
|
vm.on('playgroundData', e => {
|
||||||
|
const threads = JSON.parse(e.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 message 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 message 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 these messages, and verify they have the correct ID
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -10,7 +10,7 @@ const VariableUtil = require('../../src/util/variable-util');
|
||||||
const projectUri = path.resolve(__dirname, '../fixtures/variable_characters.sb3');
|
const projectUri = path.resolve(__dirname, '../fixtures/variable_characters.sb3');
|
||||||
const project = readFileToBuffer(projectUri);
|
const project = readFileToBuffer(projectUri);
|
||||||
|
|
||||||
test('importing sb2 project with special chars in variable names', t => {
|
test('importing sb3 project with special chars in variable names', t => {
|
||||||
const vm = new VirtualMachine();
|
const vm = new VirtualMachine();
|
||||||
vm.attachStorage(makeTestStorage());
|
vm.attachStorage(makeTestStorage());
|
||||||
|
|
||||||
|
|
|
@ -809,3 +809,35 @@ test('getAllVariableAndListReferences returns references when variable blocks ex
|
||||||
|
|
||||||
t.end();
|
t.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('getAllVariableAndListReferences does not return broadcast blocks if the flag is left out', t => {
|
||||||
|
const b = new Blocks(new Runtime());
|
||||||
|
b.createBlock(adapter(events.mockBroadcastBlock)[0]);
|
||||||
|
b.createBlock(adapter(events.mockBroadcastBlock)[1]);
|
||||||
|
|
||||||
|
t.equal(Object.keys(b.getAllVariableAndListReferences()).length, 0);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getAllVariableAndListReferences returns broadcast when we tell it to', t => {
|
||||||
|
const b = new Blocks(new Runtime());
|
||||||
|
|
||||||
|
b.createBlock(adapter(events.mockVariableBlock)[0]);
|
||||||
|
// Make the broadcast block and its shadow (which includes the actual broadcast field).
|
||||||
|
b.createBlock(adapter(events.mockBroadcastBlock)[0]);
|
||||||
|
b.createBlock(adapter(events.mockBroadcastBlock)[1]);
|
||||||
|
|
||||||
|
const varListRefs = b.getAllVariableAndListReferences(null, true);
|
||||||
|
|
||||||
|
t.equal(Object.keys(varListRefs).length, 2);
|
||||||
|
t.equal(Array.isArray(varListRefs['mock var id']), true);
|
||||||
|
t.equal(varListRefs['mock var id'].length, 1);
|
||||||
|
t.equal(varListRefs['mock var id'][0].type, Variable.SCALAR_TYPE);
|
||||||
|
t.equal(varListRefs['mock var id'][0].referencingField.value, 'a mock variable');
|
||||||
|
t.equal(Array.isArray(varListRefs['mock broadcast message id']), true);
|
||||||
|
t.equal(varListRefs['mock broadcast message id'].length, 1);
|
||||||
|
t.equal(varListRefs['mock broadcast message id'][0].type, Variable.BROADCAST_MESSAGE_TYPE);
|
||||||
|
t.equal(varListRefs['mock broadcast message id'][0].referencingField.value, 'my message');
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in a new issue