text: const test = require('tap').test;
  const VirtualMachine = require('../../src/virtual-machine.js');
  const Sprite = require('../../src/sprites/sprite.js');
  const Variable = require('../../src/engine/variable.js');
  const adapter = require('../../src/engine/adapter');
  const events = require('../fixtures/events.json');
  
  test('addSprite throws on invalid string', t => {
      const vm = new VirtualMachine();
      vm.addSprite('this is not a sprite')
          .catch(e => {
              t.equal(e.startsWith('Sprite Upload Error:'), true);
              t.end();
          });
  });
  
  test('renameSprite throws when there is no sprite with that id', t => {
      const vm = new VirtualMachine();
      vm.runtime.getTargetById = () => null;
      t.throws(
          (() => vm.renameSprite('id', 'name')),
          new Error('No target with the provided id.')
      );
      t.end();
  });
  
  test('renameSprite throws when used on a non-sprite target', t => {
      const vm = new VirtualMachine();
      const fakeTarget = {
          isSprite: () => false
      };
      vm.runtime.getTargetById = () => (fakeTarget);
      t.throws(
          (() => vm.renameSprite('id', 'name')),
          new Error('Cannot rename non-sprite targets.')
      );
      t.end();
  });
  
  test('renameSprite throws when there is no sprite for given target', t => {
      const vm = new VirtualMachine();
      const fakeTarget = {
          sprite: null,
          isSprite: () => true
      };
      vm.runtime.getTargetById = () => (fakeTarget);
      t.throws(
          (() => vm.renameSprite('id', 'name')),
          new Error('No sprite associated with this target.')
      );
      t.end();
  });
  
  test('renameSprite sets the sprite name', t => {
      const vm = new VirtualMachine();
      const fakeTarget = {
          sprite: {name: 'original'},
          isSprite: () => true
      };
      vm.runtime.getTargetById = () => (fakeTarget);
      vm.renameSprite('id', 'not-original');
      t.equal(fakeTarget.sprite.name, 'not-original');
      t.end();
  });
  
  test('renameSprite does not set sprite names to an empty string', t => {
      const vm = new VirtualMachine();
      const fakeTarget = {
          sprite: {name: 'original'},
          isSprite: () => true
      };
      vm.runtime.getTargetById = () => (fakeTarget);
      vm.renameSprite('id', '');
      t.equal(fakeTarget.sprite.name, 'original');
      t.end();
  });
  
  test('renameSprite does not set sprite names to reserved names', t => {
      const vm = new VirtualMachine();
      const fakeTarget = {
          sprite: {name: 'original'},
          isSprite: () => true
      };
      vm.runtime.getTargetById = () => (fakeTarget);
      vm.renameSprite('id', '_mouse_');
      t.equal(fakeTarget.sprite.name, 'original');
      t.end();
  });
  
  test('renameSprite increments from existing sprite names', t => {
      const vm = new VirtualMachine();
      vm.emitTargetsUpdate = () => {};
  
      const spr1 = new Sprite(null, vm.runtime);
      const target1 = spr1.createClone();
      const spr2 = new Sprite(null, vm.runtime);
      const target2 = spr2.createClone();
  
      vm.runtime.targets = [target1, target2];
      vm.renameSprite(target1.id, 'foo');
      t.equal(vm.runtime.targets[0].sprite.name, 'foo');
      vm.renameSprite(target2.id, 'foo');
      t.equal(vm.runtime.targets[1].sprite.name, 'foo2');
      t.end();
  });
  
  test('renameSprite does not increment when renaming to the same name', t => {
      const vm = new VirtualMachine();
      vm.emitTargetsUpdate = () => {};
  
      const spr = new Sprite(null, vm.runtime);
      spr.name = 'foo';
      const target = spr.createClone();
  
      vm.runtime.targets = [target];
  
      t.equal(vm.runtime.targets[0].sprite.name, 'foo');
      vm.renameSprite(target.id, 'foo');
      t.equal(vm.runtime.targets[0].sprite.name, 'foo');
  
      t.end();
  });
  
  test('deleteSprite throws when used on a non-sprite target', t => {
      const vm = new VirtualMachine();
      vm.runtime.targets = [{
          id: 'id',
          isSprite: () => false
      }];
      t.throws(
          (() => vm.deleteSprite('id')),
          new Error('Cannot delete non-sprite targets.')
      );
      t.end();
  });
  
  test('deleteSprite throws when there is no sprite for the given target', t => {
      const vm = new VirtualMachine();
      vm.runtime.targets = [{
          id: 'id',
          isSprite: () => true,
          sprite: null
      }];
      t.throws(
          (() => vm.deleteSprite('id')),
          new Error('No sprite associated with this target.')
      );
      t.end();
  });
  
  test('deleteSprite throws when there is no target with given id', t => {
      const vm = new VirtualMachine();
      vm.runtime.targets = [{
          id: 'id',
          isSprite: () => true,
          sprite: {
              name: 'this name'
          }
      }];
      t.throws(
          (() => vm.deleteSprite('id1')),
          new Error('No target with the provided id.')
      );
      t.end();
  });
  
  test('deleteSprite deletes a sprite when given id is associated with a known sprite', t => {
      const vm = new VirtualMachine();
      const spr = new Sprite(null, vm.runtime);
      const currTarget = spr.createClone();
  
      vm.runtime.targets = [currTarget];
  
      t.equal(currTarget.sprite.clones.length, 1);
      vm.deleteSprite(currTarget.id);
      t.equal(currTarget.sprite.clones.length, 0);
      t.end();
  });
  
  // eslint-disable-next-line max-len
  test('deleteSprite sets editing target as null when given sprite is current editing target, and the only target in the runtime', t => {
      const vm = new VirtualMachine();
      const spr = new Sprite(null, vm.runtime);
      const currTarget = spr.createClone();
  
      vm.editingTarget = currTarget;
      vm.runtime.targets = [currTarget];
  
      vm.deleteSprite(currTarget.id);
  
      t.equal(vm.runtime.targets.length, 0);
      t.equal(vm.editingTarget, null);
      t.end();
  });
  
  // eslint-disable-next-line max-len
  test('deleteSprite updates editingTarget when sprite being deleted is current editing target, and there is another target in the runtime', t => {
      const vm = new VirtualMachine();
      const spr1 = new Sprite(null, vm.runtime);
      const spr2 = new Sprite(null, vm.runtime);
      const currTarget = spr1.createClone();
      const otherTarget = spr2.createClone();
  
      vm.emitWorkspaceUpdate = () => null;
  
      vm.runtime.targets = [currTarget, otherTarget];
      vm.editingTarget = currTarget;
  
      t.equal(vm.runtime.targets.length, 2);
      vm.deleteSprite(currTarget.id);
      t.equal(vm.runtime.targets.length, 1);
      t.equal(vm.editingTarget.id, otherTarget.id);
  
      // now let's try them in the other order in the runtime.targets list
  
      // can't reuse deleted targets
      const currTarget2 = spr1.createClone();
      const otherTarget2 = spr2.createClone();
  
      vm.runtime.targets = [otherTarget2, currTarget2];
      vm.editingTarget = currTarget2;
  
      t.equal(vm.runtime.targets.length, 2);
      vm.deleteSprite(currTarget2.id);
      t.equal(vm.editingTarget.id, otherTarget2.id);
      t.equal(vm.runtime.targets.length, 1);
  
      t.end();
  });
  
  test('duplicateSprite throws when there is no target with given id', t => {
      const vm = new VirtualMachine();
      vm.runtime.targets = [{
          id: 'id',
          isSprite: () => true,
          sprite: {
              name: 'this name'
          }
      }];
      t.throws(
          (() => vm.duplicateSprite('id1')),
          new Error('No target with the provided id')
      );
      t.end();
  });
  
  test('duplicateSprite throws when used on a non-sprite target', t => {
      const vm = new VirtualMachine();
      vm.runtime.targets = [{
          id: 'id',
          isSprite: () => false
      }];
      t.throws(
          (() => vm.duplicateSprite('id')),
          new Error('Cannot duplicate non-sprite targets.')
      );
      t.end();
  });
  
  test('duplicateSprite throws when there is no sprite for the given target', t => {
      const vm = new VirtualMachine();
      vm.runtime.targets = [{
          id: 'id',
          isSprite: () => true,
          sprite: null
      }];
      t.throws(
          (() => vm.duplicateSprite('id')),
          new Error('No sprite associated with this target.')
      );
      t.end();
  });
  
  test('duplicateSprite duplicates a sprite when given id is associated with known sprite', t => {
      const vm = new VirtualMachine();
      const spr = new Sprite(null, vm.runtime);
      const currTarget = spr.createClone();
      vm.editingTarget = currTarget;
  
      vm.emitWorkspaceUpdate = () => null;
  
      vm.runtime.targets = [currTarget];
      t.equal(vm.runtime.targets.length, 1);
      vm.duplicateSprite(currTarget.id).then(() => {
          t.equal(vm.runtime.targets.length, 2);
          t.end();
      });
  
  });
  
  test('duplicateSprite assigns duplicated sprite a fresh name', t => {
      const vm = new VirtualMachine();
      const spr = new Sprite(null, vm.runtime);
      spr.name = 'sprite1';
      const currTarget = spr.createClone();
      vm.editingTarget = currTarget;
  
      vm.emitWorkspaceUpdate = () => null;
  
      vm.runtime.targets = [currTarget];
      t.equal(vm.runtime.targets.length, 1);
      vm.duplicateSprite(currTarget.id).then(() => {
          t.equal(vm.runtime.targets.length, 2);
          t.equal(vm.runtime.targets[0].sprite.name, 'sprite1');
          t.equal(vm.runtime.targets[1].sprite.name, 'sprite2');
          t.end();
      });
  
  });
  
  test('reorderCostume', t => {
      const vm = new VirtualMachine();
      vm.emitTargetsUpdate = () => {};
  
      const spr = new Sprite(null, vm.runtime);
      spr.name = 'foo';
      const target = spr.createClone();
  
      // Stub out reorder on target, tested in rendered-target tests.
      // Just want to know if it is called with the right params.
      let costumeIndex = null;
      let newIndex = null;
      target.reorderCostume = (_costumeIndex, _newIndex) => {
          costumeIndex = _costumeIndex;
          newIndex = _newIndex;
          return true; // Do not need all the logic about if a reorder occurred.
      };
  
      vm.runtime.targets = [target];
  
      t.equal(vm.reorderCostume('not-a-target', 0, 3), false);
      t.equal(costumeIndex, null);
      t.equal(newIndex, null);
  
      t.equal(vm.reorderCostume(target.id, 0, 3), true);
      t.equal(costumeIndex, 0);
      t.equal(newIndex, 3);
  
      t.end();
  });
  
  test('reorderSound', t => {
      const vm = new VirtualMachine();
      vm.emitTargetsUpdate = () => {};
  
      const spr = new Sprite(null, vm.runtime);
      spr.name = 'foo';
      const target = spr.createClone();
  
      // Stub out reorder on target, tested in rendered-target tests.
      // Just want to know if it is called with the right params.
      let soundIndex = null;
      let newIndex = null;
      target.reorderSound = (_soundIndex, _newIndex) => {
          soundIndex = _soundIndex;
          newIndex = _newIndex;
          return true; // Do not need all the logic about if a reorder occurred.
      };
  
      vm.runtime.targets = [target];
  
      t.equal(vm.reorderSound('not-a-target', 0, 3), false);
      t.equal(soundIndex, null); // Make sure reorder function was not called somehow.
      t.equal(newIndex, null);
  
      t.equal(vm.reorderSound(target.id, 0, 3), true);
      t.equal(soundIndex, 0); // Make sure reorder function was called correctly.
      t.equal(newIndex, 3);
  
      t.end();
  });
  
  test('shareCostumeToTarget', t => {
      const vm = new VirtualMachine();
      const spr1 = new Sprite(null, vm.runtime);
      spr1.name = 'foo';
      const target1 = spr1.createClone();
      const costume1 = {name: 'costume1'};
      target1.addCostume(costume1);
  
      const spr2 = new Sprite(null, vm.runtime);
      spr2.name = 'foo';
      const target2 = spr2.createClone();
      const costume2 = {name: 'another costume'};
      target2.addCostume(costume2);
  
      vm.runtime.targets = [target1, target2];
      vm.editingTarget = vm.runtime.targets[0];
      vm.emitWorkspaceUpdate = () => null;
  
      vm.shareCostumeToTarget(0, target2.id).then(() => {
          t.equal(target2.currentCostume, 1);
          t.equal(target2.getCostumes()[1].name, 'costume1');
          t.end();
      });
  });
  
  test('shareSoundToTarget', t => {
      const vm = new VirtualMachine();
      const spr1 = new Sprite(null, vm.runtime);
      spr1.name = 'foo';
      const target1 = spr1.createClone();
      const sound1 = {name: 'sound1'};
      target1.addSound(sound1);
  
      const spr2 = new Sprite(null, vm.runtime);
      spr2.name = 'foo';
      const target2 = spr2.createClone();
      const sound2 = {name: 'another sound'};
      target2.addSound(sound2);
  
      vm.runtime.targets = [target1, target2];
      vm.editingTarget = vm.runtime.targets[0];
      vm.emitWorkspaceUpdate = () => null;
  
      vm.shareSoundToTarget(0, target2.id).then(() => {
          t.equal(target2.getSounds()[1].name, 'sound1');
          t.end();
      });
  });
  
  test('reorderTarget', t => {
      const vm = new VirtualMachine();
      vm.emitTargetsUpdate = () => {};
  
      vm.runtime.targets = ['a', 'b', 'c', 'd'];
  
      t.equal(vm.reorderTarget(2, 2), false);
      t.deepEqual(vm.runtime.targets, ['a', 'b', 'c', 'd']);
  
      // Make sure clamping works
      t.equal(vm.reorderTarget(-100, -5), false);
      t.deepEqual(vm.runtime.targets, ['a', 'b', 'c', 'd']);
  
      // Reorder upwards
      t.equal(vm.reorderTarget(0, 2), true);
      t.deepEqual(vm.runtime.targets, ['b', 'c', 'a', 'd']);
  
      // Reorder downwards
      t.equal(vm.reorderTarget(3, 1), true);
      t.deepEqual(vm.runtime.targets, ['b', 'd', 'c', 'a']);
  
      t.end();
  });
  
  test('emitWorkspaceUpdate', t => {
      const vm = new VirtualMachine();
      const blocksToXML = comments => {
          let blockString = 'blocks\n';
          if (comments) {
              for (const commentId in comments) {
                  const comment = comments[commentId];
                  blockString += `A Block Comment: ${comment.toXML()}`;
              }
  
          }
          return blockString;
      };
      vm.runtime.targets = [
          {
              isStage: true,
              variables: {
                  global: {
                      toXML: () => 'global'
                  }
              },
              blocks: {
                  toXML: blocksToXML
              },
              comments: {
                  aStageComment: {
                      toXML: () => 'aStageComment',
                      blockId: null
                  }
              }
          }, {
              variables: {
                  unused: {
                      toXML: () => 'unused'
                  }
              },
              blocks: {
                  toXML: blocksToXML
              },
              comments: {
                  someBlockComment: {
                      toXML: () => 'someBlockComment',
                      blockId: 'someBlockId'
                  }
              }
          }, {
              variables: {
                  local: {
                      toXML: () => 'local'
                  }
              },
              blocks: {
                  toXML: blocksToXML
              },
              comments: {
                  someOtherComment: {
                      toXML: () => 'someOtherComment',
                      blockId: null
                  },
                  aBlockComment: {
                      toXML: () => 'aBlockComment',
                      blockId: 'a block'
                  }
              }
          }
      ];
      vm.editingTarget = vm.runtime.targets[2];
  
      let xml = null;
      vm.emit = (event, data) => (xml = data.xml);
      vm.emitWorkspaceUpdate();
      t.notEqual(xml.indexOf('global'), -1);
      t.notEqual(xml.indexOf('local'), -1);
      t.equal(xml.indexOf('unused'), -1);
      t.notEqual(xml.indexOf('blocks'), -1);
      t.equal(xml.indexOf('aStageComment'), -1);
      t.equal(xml.indexOf('someBlockComment'), -1);
      t.notEqual(xml.indexOf('someOtherComment'), -1);
      t.notEqual(xml.indexOf('A Block Comment: aBlockComment'), -1);
      t.end();
  });
  
  test('drag IO redirect', t => {
      const vm = new VirtualMachine();
      const sprite1Info = [];
      const sprite2Info = [];
      vm.runtime.targets = [
          {
              id: 'sprite1',
              postSpriteInfo: data => sprite1Info.push(data)
          }, {
              id: 'sprite2',
              postSpriteInfo: data => sprite2Info.push(data),
              startDrag: () => {},
              stopDrag: () => {}
          }
      ];
      vm.editingTarget = vm.runtime.targets[0];
      // Stub emitWorkspace/TargetsUpdate, it needs data we don't care about here
      vm.emitWorkspaceUpdate = () => null;
      vm.emitTargetsUpdate = () => null;
  
      // postSpriteInfo should go to the editing target by default``
      vm.postSpriteInfo('sprite1 info');
      t.equal(sprite1Info[0], 'sprite1 info');
  
      // postSprite info goes to the drag target if it exists
      vm.startDrag('sprite2');
      vm.postSpriteInfo('sprite2 info');
      t.equal(sprite2Info[0], 'sprite2 info');
  
      // stop drag should set the editing target
      vm.stopDrag('sprite2');
      t.equal(vm.editingTarget.id, 'sprite2');
  
      // Then postSpriteInfo should continue posting to the new editing target
      vm.postSpriteInfo('sprite2 info 2');
      t.equal(sprite2Info[1], 'sprite2 info 2');
      t.end();
  });
  
  test('select original after dragging clone', t => {
      const vm = new VirtualMachine();
      let newEditingTargetId = null;
      vm.setEditingTarget = id => {
          newEditingTargetId = id;
      };
      vm.runtime.targets = [
          {
              id: 'sprite1_clone',
              sprite: {clones: [{id: 'sprite1_original'}]},
              stopDrag: () => {}
          }, {
              id: 'sprite2',
              stopDrag: () => {}
          }
      ];
  
      // Stop drag on a bare target selects that target
      vm.stopDrag('sprite2');
      t.equal(newEditingTargetId, 'sprite2');
  
      // Stop drag on target with parent sprite selects the 0th clone of that sprite
      vm.stopDrag('sprite1_clone');
      t.equal(newEditingTargetId, 'sprite1_original');
      t.end();
  });
  
  test('setVariableValue', t => {
      const vm = new VirtualMachine();
      const spr = new Sprite(null, vm.runtime);
      const target = spr.createClone();
      target.createVariable('a-variable', 'a-name', Variable.SCALAR_TYPE);
  
      vm.runtime.targets = [target];
  
      // Returns false if there is no variable to set
      t.equal(vm.setVariableValue(target.id, 'not-a-variable', 100), false);
  
      // Returns false if there is no target with that id
      t.equal(vm.setVariableValue('not-a-target', 'a-variable', 100), false);
  
      // Returns true and updates the value if variable is present
      t.equal(vm.setVariableValue(target.id, 'a-variable', 100), true);
      t.equal(target.lookupVariableById('a-variable').value, 100);
  
      t.end();
  });
  
  test('getVariableValue', t => {
      const vm = new VirtualMachine();
      const spr = new Sprite(null, vm.runtime);
      const target = spr.createClone();
      target.createVariable('a-variable', 'a-name', Variable.SCALAR_TYPE);
  
      vm.runtime.targets = [target];
  
      // Returns null if there is no variable with that id
      t.equal(vm.getVariableValue(target.id, 'not-a-variable'), null);
  
      // Returns null if there is no target with that id
      t.equal(vm.getVariableValue('not-a-target', 'a-variable'), null);
  
      // Returns true and updates the value if variable is present
      t.equal(vm.getVariableValue(target.id, 'a-variable'), 0);
      vm.setVariableValue(target.id, 'a-variable', 'string');
      t.equal(vm.getVariableValue(target.id, 'a-variable'), 'string');
  
      t.end();
  });
  
  // Block Listener tests for comment
  test('comment_create event updates comment with null position', t => {
      const vm = new VirtualMachine();
      const spr = new Sprite(null, vm.runtime);
      const target = spr.createClone();
  
      target.createComment('a comment', null, 'some text',
          null, null, 200, 300, false);
      vm.runtime.targets = [target];
      vm.editingTarget = target;
      vm.runtime.setEditingTarget(target);
  
      const comment = target.comments['a comment'];
      t.equal(comment.x, null);
      t.equal(comment.y, null);
  
      vm.blockListener(events.createcommentUpdatePosition);
  
      t.equal(comment.x, 10);
      t.equal(comment.y, 20);
  
      t.end();
  });
  
  test('shareBlocksToTarget shares global variables without any name changes', t => {
      const vm = new VirtualMachine();
      const runtime = vm.runtime;
      const spr1 = new Sprite(null, runtime);
      const stage = spr1.createClone();
      stage.isStage = true;
  
      const spr2 = new Sprite(null, runtime);
      const target = spr2.createClone();
  
      runtime.targets = [stage, target];
      vm.editingTarget = target;
      vm.runtime.setEditingTarget(target);
  
      stage.createVariable('mock var id', 'a mock variable', Variable.SCALAR_TYPE);
      t.equal(Object.keys(target.variables).length, 0);
      t.equal(Object.keys(stage.variables).length, 1);
      t.equal(stage.variables['mock var id'].name, 'a mock variable');
  
  
      vm.setVariableValue(stage.id, 'mock var id', 10);
      t.equal(vm.getVariableValue(stage.id, 'mock var id'), 10);
  
      target.blocks.createBlock(adapter(events.mockVariableBlock)[0]);
  
      // Verify the block exists on the target, and that it references the global variable
      t.type(target.blocks.getBlock('a block'), 'object');
      t.type(target.blocks.getBlock('a block').fields, 'object');
      t.type(target.blocks.getBlock('a block').fields.VARIABLE, 'object');
      t.equal(target.blocks.getBlock('a block').fields.VARIABLE.id, 'mock var id');
  
      // Verify that the block does not exist on the stage
      t.type(stage.blocks.getBlock('a block'), 'undefined');
  
      // Share the block to the stage
      vm.shareBlocksToTarget([target.blocks.getBlock('a block')], stage.id, target.id);
  
      // Verify that the block now exists on the target as well as the stage
      t.type(target.blocks.getBlock('a block'), 'object');
      t.type(target.blocks.getBlock('a block').fields, 'object');
      t.type(target.blocks.getBlock('a block').fields.VARIABLE, 'object');
      t.equal(target.blocks.getBlock('a block').fields.VARIABLE.id, 'mock var id');
  
      t.type(stage.blocks.getBlock('a block'), 'object');
      t.type(stage.blocks.getBlock('a block').fields, 'object');
      t.type(stage.blocks.getBlock('a block').fields.VARIABLE, 'object');
      t.equal(stage.blocks.getBlock('a block').fields.VARIABLE.id, 'mock var id');
  
      // Verify that the variables haven't changed, the variable still exists on the
      // stage, it should still have the same name and value, and there should be
      // no variables on the target.
      t.equal(Object.keys(target.variables).length, 0);
      t.equal(Object.keys(stage.variables).length, 1);
      t.equal(stage.variables['mock var id'].name, 'a mock variable');
      t.equal(vm.getVariableValue(stage.id, 'mock var id'), 10);
  
      t.end();
  });
  
  test('shareBlocksToTarget shares a local variable to the stage, creating a global variable with a new name', t => {
      const vm = new VirtualMachine();
      const runtime = vm.runtime;
      const spr1 = new Sprite(null, runtime);
      const stage = spr1.createClone();
      stage.isStage = true;
  
      const spr2 = new Sprite(null, runtime);
      const target = spr2.createClone();
  
      runtime.targets = [stage, target];
      vm.editingTarget = target;
      vm.runtime.setEditingTarget(target);
  
      target.createVariable('mock var id', 'a mock variable', Variable.SCALAR_TYPE);
      t.equal(Object.keys(stage.variables).length, 0);
      t.equal(Object.keys(target.variables).length, 1);
      t.equal(target.variables['mock var id'].name, 'a mock variable');
  
  
      vm.setVariableValue(target.id, 'mock var id', 10);
      t.equal(vm.getVariableValue(target.id, 'mock var id'), 10);
  
      target.blocks.createBlock(adapter(events.mockVariableBlock)[0]);
  
      // Verify the block exists on the target, and that it references the global variable
      t.type(target.blocks.getBlock('a block'), 'object');
      t.type(target.blocks.getBlock('a block').fields, 'object');
      t.type(target.blocks.getBlock('a block').fields.VARIABLE, 'object');
      t.equal(target.blocks.getBlock('a block').fields.VARIABLE.id, 'mock var id');
  
      // Verify that the block does not exist on the stage
      t.type(stage.blocks.getBlock('a block'), 'undefined');
  
      // Share the block to the stage
      vm.shareBlocksToTarget([target.blocks.getBlock('a block')], stage.id, target.id);
  
      // Verify that the block still exists on the target and remains unchanged
      t.type(target.blocks.getBlock('a block'), 'object');
      t.type(target.blocks.getBlock('a block').fields, 'object');
      t.type(target.blocks.getBlock('a block').fields.VARIABLE, 'object');
      t.equal(target.blocks.getBlock('a block').fields.VARIABLE.id, 'mock var id');
  
      t.type(stage.blocks.getBlock('a block'), 'object');
      t.type(stage.blocks.getBlock('a block').fields, 'object');
      t.type(stage.blocks.getBlock('a block').fields.VARIABLE, 'object');
      t.equal(stage.blocks.getBlock('a block').fields.VARIABLE.id, 'StageVarFromLocal_mock var id');
  
      // Verify that a new global variable was created, the old one still exists on
      // the target and still has the same name and value, and the new one has
      // a new name and value 0.
      t.equal(Object.keys(target.variables).length, 1);
      t.equal(target.variables['mock var id'].name, 'a mock variable');
      t.equal(vm.getVariableValue(target.id, 'mock var id'), 10);
  
      // Verify that a new variable was created on the stage, with a new name and new id
      t.equal(Object.keys(stage.variables).length, 1);
      t.type(stage.variables['mock var id'], 'undefined');
      const newGlobalVar = Object.values(stage.variables)[0];
      t.equal(newGlobalVar.name, 'Stage: a mock variable');
      const newId = newGlobalVar.id;
      t.notEqual(newId, 'mock var id');
      t.equals(vm.getVariableValue(stage.id, newId), 0);
  
      t.end();
  });
  
  test('shareBlocksToTarget chooses a fresh name for a new global variable checking for conflicts on all sprites', t => {
      const vm = new VirtualMachine();
      const runtime = vm.runtime;
      const spr1 = new Sprite(null, runtime);
      const stage = spr1.createClone();
      stage.isStage = true;
  
      const spr2 = new Sprite(null, runtime);
      const target = spr2.createClone();
  
      const spr3 = new Sprite(null, runtime);
      const otherTarget = spr3.createClone();
  
      runtime.targets = [stage, target, otherTarget];
      vm.editingTarget = target;
      vm.runtime.setEditingTarget(target);
  
      target.createVariable('mock var id', 'a mock variable', Variable.SCALAR_TYPE);
      t.equal(Object.keys(stage.variables).length, 0);
      t.equal(Object.keys(target.variables).length, 1);
      t.equal(target.variables['mock var id'].name, 'a mock variable');
  
  
      vm.setVariableValue(target.id, 'mock var id', 10);
      t.equal(vm.getVariableValue(target.id, 'mock var id'), 10);
  
      target.blocks.createBlock(adapter(events.mockVariableBlock)[0]);
  
      // Verify the block exists on the target, and that it references the global variable
      t.type(target.blocks.getBlock('a block'), 'object');
      t.type(target.blocks.getBlock('a block').fields, 'object');
      t.type(target.blocks.getBlock('a block').fields.VARIABLE, 'object');
      t.equal(target.blocks.getBlock('a block').fields.VARIABLE.id, 'mock var id');
  
      // Verify that the block does not exist on the stage
      t.type(stage.blocks.getBlock('a block'), 'undefined');
  
      // Create a variable that conflicts with what will be the new name for the
      // new global variable to ensure a fresh name is chosen
      otherTarget.createVariable('a different var', 'Stage: a mock variable', Variable.SCALAR_TYPE);
  
      // Share the block to the stage
      vm.shareBlocksToTarget([target.blocks.getBlock('a block')], stage.id, target.id);
  
      // Verify that the block still exists on the target and remains unchanged
      t.type(target.blocks.getBlock('a block'), 'object');
      t.type(target.blocks.getBlock('a block').fields, 'object');
      t.type(target.blocks.getBlock('a block').fields.VARIABLE, 'object');
      t.equal(target.blocks.getBlock('a block').fields.VARIABLE.id, 'mock var id');
  
      t.type(stage.blocks.getBlock('a block'), 'object');
      t.type(stage.blocks.getBlock('a block').fields, 'object');
      t.type(stage.blocks.getBlock('a block').fields.VARIABLE, 'object');
      t.equal(stage.blocks.getBlock('a block').fields.VARIABLE.id, 'StageVarFromLocal_mock var id');
  
      // Verify that a new global variable was created, the old one still exists on
      // the target and still has the same name and value, and the new one has
      // a new name and value 0.
      t.equal(Object.keys(target.variables).length, 1);
      t.equal(target.variables['mock var id'].name, 'a mock variable');
      t.equal(vm.getVariableValue(target.id, 'mock var id'), 10);
  
      // Verify that a new variable was created on the stage, with a new name and new id
      t.equal(Object.keys(stage.variables).length, 1);
      t.type(stage.variables['mock var id'], 'undefined');
      const newGlobalVar = Object.values(stage.variables)[0];
      t.equal(newGlobalVar.name, 'Stage: a mock variable2');
      const newId = newGlobalVar.id;
      t.notEqual(newId, 'mock var id');
      t.equals(vm.getVariableValue(stage.id, newId), 0);
  
      t.end();
  });
  
  test('Setting turbo mode emits events', t => {
      let turboMode = null;
  
      const vm = new VirtualMachine();
  
      vm.addListener('TURBO_MODE_ON', () => {
          turboMode = true;
      });
      vm.addListener('TURBO_MODE_OFF', () => {
          turboMode = false;
      });
  
      vm.setTurboMode(true);
      t.equal(turboMode, true);
  
      vm.setTurboMode(false);
      t.equal(turboMode, false);
  
      t.end();
  });
  