diff --git a/src/serialization/sb2.js b/src/serialization/sb2.js index 4c269448f..b0eec1a05 100644 --- a/src/serialization/sb2.js +++ b/src/serialization/sb2.js @@ -183,23 +183,6 @@ const parseScripts = function (scripts, blocks, addBroadcastMsg, getVariableId, blocks.createBlock(convertedBlocks[j]); } } - - // scriptIndexForComment is now greater than the index of the 'last' block in the flattened block array. - // If there are any comments referring to this index or any indices after it, they are comments that - // were originally created as block comments, detached from the block, and then had the associated block deleted. - // These comments should be imported as workspace comments - // by making their blockIDs (which currently refer to non-existing blocks) - // null (See #1452). - const blockCommentIndicesToFix = Object.keys(comments).filter(k => k >= scriptIndexForComment); - for (let i = 0; i < blockCommentIndicesToFix.length; i++) { - const currCommentIndex = blockCommentIndicesToFix[i]; - const currBlockComments = comments[currCommentIndex]; - currBlockComments.forEach(c => { - if (typeof c.blockId === 'number') { - c.blockId = null; - } - }); - } }; /** @@ -523,6 +506,22 @@ const parseScratchObject = function (object, runtime, extensions, topLevel, zip) parseScripts(object.scripts, blocks, addBroadcastMsg, getVariableId, extensions, blockComments); } + // If there are any comments referring to a numerical block ID, make them + // workspace comments. These are comments that were originally created as + // block comments, detached from the block, and then had the associated + // block deleted. + // These comments should be imported as workspace comments + // by making their blockIDs (which currently refer to non-existing blocks) + // null (See #1452). + for (const commentIndex in blockComments) { + const currBlockComments = blockComments[commentIndex]; + currBlockComments.forEach(c => { + if (typeof c.blockId === 'number') { + c.blockId = null; + } + }); + } + // Update stage specific blocks (e.g. sprite clicked <=> stage clicked) blocks.updateTargetSpecificBlocks(topLevel); // topLevel = isStage diff --git a/test/fixtures/block-to-workspace-comments-without-scripts.sb2 b/test/fixtures/block-to-workspace-comments-without-scripts.sb2 new file mode 100644 index 000000000..70d29c1b0 Binary files /dev/null and b/test/fixtures/block-to-workspace-comments-without-scripts.sb2 differ diff --git a/test/integration/block_to_workspace_comment_import_no_scripts.js b/test/integration/block_to_workspace_comment_import_no_scripts.js new file mode 100644 index 000000000..95ac37672 --- /dev/null +++ b/test/integration/block_to_workspace_comment_import_no_scripts.js @@ -0,0 +1,59 @@ +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 projectUri = path.resolve(__dirname, '../fixtures/block-to-workspace-comments-without-scripts.sb2'); +const project = readFileToBuffer(projectUri); + +/* eslint-disable-next-line max-len */ +test('importing sb2 project where block comment is converted to workspace comment and block is deleted, and there are no scripts on the workspace', 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); + + const target = vm.runtime.targets[1]; + + // Sprite 1 has 1 comments, a workspace comment which was + // originally created via a block comment to workspace comment conversion in Scratch 2.0. + // What differentiates this test from above is that there are no scripts in this project. + const targetComments = Object.values(target.comments); + t.equal(targetComments.length, 1); + const spriteWorkspaceComments = targetComments.filter(comment => comment.blockId === null); + t.equal(spriteWorkspaceComments.length, 1); + + // Test the sprite block comments + const blockComments = targetComments.filter(comment => !!comment.blockId); + t.equal(blockComments.length, 0); + + // There should not be any comments where blockId is a number + const invalidComments = targetComments.filter(comment => typeof comment.blockId === 'number'); + t.equal(invalidComments.length, 0); + + const targetBlocks = Object.values(target.blocks._blocks); + t.equal(targetBlocks.length, 0); + + 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); + }); + }); +});