/** * This test mocks render breaking on loading a sprite with a * corrupted vector costume. * The VM should handle this safely by displaying a Gray Question Mark, * but keeping track of the original costume data and serializing the * original costume data back out. The saved project.json should not * reflect that the costume is broken and should therefore re-attempt * to load the costume if the saved project is re-loaded. */ const path = require('path'); const tap = require('tap'); const md5 = require('js-md5'); const makeTestStorage = require('../fixtures/make-test-storage'); const FakeRenderer = require('../fixtures/fake-renderer'); const {extractAsset, readFileToBuffer} = require('../fixtures/readProjectFile'); const VirtualMachine = require('../../src/index'); const {serializeCostumes} = require('../../src/serialization/serialize-assets'); const projectUri = path.resolve(__dirname, '../fixtures/default.sb3'); const project = readFileToBuffer(projectUri); const spriteUri = path.resolve(__dirname, '../fixtures/corrupt_svg.sprite3'); const sprite = readFileToBuffer(spriteUri); const costumeFileName = 'a267f8b97ee9cf8aa9832aa0b4cfd9eb.svg'; const originalCostume = extractAsset(spriteUri, costumeFileName); // We need to get the actual md5 because we hand modified the svg to corrupt it // after we downloaded the project from Scratch // Loading the project back into the VM will correct the assetId and md5 const brokenCostumeMd5 = md5(originalCostume); let vm; let defaultVectorAssetId; tap.beforeEach(() => { const storage = makeTestStorage(); vm = new VirtualMachine(); vm.attachStorage(storage); defaultVectorAssetId = vm.runtime.storage.defaultAssetId.ImageVector; // Mock renderer breaking on loading a corrupt costume FakeRenderer.prototype.createSVGSkin = function (svgString) { // Look for text added to costume to make it a corrupt svg if (svgString.includes(' vm.addSprite(sprite)); }); const test = tap.test; test('load sprite3 with corrupted vector costume file', t => { t.equal(vm.runtime.targets.length, 3); const stage = vm.runtime.targets[0]; t.ok(stage.isStage); const blueGuySprite = vm.runtime.targets[2]; t.equal(blueGuySprite.getName(), 'Blue Square Guy'); t.equal(blueGuySprite.getCostumes().length, 1); const corruptedCostume = blueGuySprite.getCostumes()[0]; t.equal(corruptedCostume.name, 'costume1'); t.equal(corruptedCostume.assetId, defaultVectorAssetId); t.equal(corruptedCostume.dataFormat, 'svg'); // Runtime should have info about broken asset t.ok(corruptedCostume.broken); t.equal(corruptedCostume.broken.assetId, brokenCostumeMd5); // Verify that we saved the original asset data t.equal(md5(corruptedCostume.broken.asset.data), brokenCostumeMd5); t.end(); }); test('load and then save sprite with corrupted costume file', t => { const resavedSprite = JSON.parse(vm.toJSON(vm.runtime.targets[2].id)); t.equal(resavedSprite.name, 'Blue Square Guy'); t.equal(resavedSprite.costumes.length, 1); const corruptedCostume = resavedSprite.costumes[0]; t.equal(corruptedCostume.name, 'costume1'); // Resaved project costume should have the metadata that corresponds to the original broken costume t.equal(corruptedCostume.assetId, brokenCostumeMd5); t.equal(corruptedCostume.dataFormat, 'svg'); // Test that we didn't save any data about the costume being broken t.notOk(corruptedCostume.broken); t.end(); }); test('serializeCostume saves orignal broken costume', t => { const costumeDescs = serializeCostumes(vm.runtime, vm.runtime.targets[2].id); t.equal(costumeDescs.length, 1); const costume = costumeDescs[0]; t.equal(costume.fileName, `${brokenCostumeMd5}.svg`); t.equal(md5(costume.fileContent), brokenCostumeMd5); t.end(); });