Spaces:
Runtime error
Runtime error
| const test = require('tap').test; | |
| const Blocks = require('../../src/engine/blocks'); | |
| const Variable = require('../../src/engine/variable'); | |
| const adapter = require('../../src/engine/adapter'); | |
| const events = require('../fixtures/events.json'); | |
| const Runtime = require('../../src/engine/runtime'); | |
| test('spec', t => { | |
| const b = new Blocks(new Runtime()); | |
| t.type(Blocks, 'function'); | |
| t.type(b, 'object'); | |
| t.ok(b instanceof Blocks); | |
| t.type(b._blocks, 'object'); | |
| t.type(b._scripts, 'object'); | |
| t.ok(Array.isArray(b._scripts)); | |
| t.type(b.createBlock, 'function'); | |
| t.type(b.moveBlock, 'function'); | |
| t.type(b.changeBlock, 'function'); | |
| t.type(b.deleteBlock, 'function'); | |
| t.type(b.getBlock, 'function'); | |
| t.type(b.getScripts, 'function'); | |
| t.type(b.getNextBlock, 'function'); | |
| t.type(b.getBranch, 'function'); | |
| t.type(b.getOpcode, 'function'); | |
| t.type(b.mutationToXML, 'function'); | |
| t.type(b.updateSensingOfReference, 'function'); | |
| t.end(); | |
| }); | |
| // Getter tests | |
| test('getBlock', t => { | |
| const b = new Blocks(new Runtime()); | |
| b.createBlock({ | |
| id: 'foo', | |
| opcode: 'TEST_BLOCK', | |
| next: null, | |
| fields: {}, | |
| inputs: {}, | |
| topLevel: true | |
| }); | |
| const block = b.getBlock('foo'); | |
| t.type(block, 'object'); | |
| const notBlock = b.getBlock('?'); | |
| t.type(notBlock, 'undefined'); | |
| t.end(); | |
| }); | |
| test('getScripts', t => { | |
| const b = new Blocks(new Runtime()); | |
| let scripts = b.getScripts(); | |
| t.type(scripts, 'object'); | |
| t.equals(scripts.length, 0); | |
| // Create two top-level blocks and one not. | |
| b.createBlock({ | |
| id: 'foo', | |
| opcode: 'TEST_BLOCK', | |
| next: null, | |
| fields: {}, | |
| inputs: {}, | |
| topLevel: true | |
| }); | |
| b.createBlock({ | |
| id: 'foo2', | |
| opcode: 'TEST_BLOCK', | |
| next: null, | |
| fields: {}, | |
| inputs: {}, | |
| topLevel: true | |
| }); | |
| b.createBlock({ | |
| id: 'foo3', | |
| opcode: 'TEST_BLOCK', | |
| next: null, | |
| fields: {}, | |
| inputs: {}, | |
| topLevel: false | |
| }); | |
| scripts = b.getScripts(); | |
| t.type(scripts, 'object'); | |
| t.equals(scripts.length, 2); | |
| t.ok(scripts.indexOf('foo') > -1); | |
| t.ok(scripts.indexOf('foo2') > -1); | |
| t.equals(scripts.indexOf('foo3'), -1); | |
| t.end(); | |
| }); | |
| test('getNextBlock', t => { | |
| const b = new Blocks(new Runtime()); | |
| b.createBlock({ | |
| id: 'foo', | |
| opcode: 'TEST_BLOCK', | |
| next: null, | |
| fields: {}, | |
| inputs: {}, | |
| topLevel: true | |
| }); | |
| let next = b.getNextBlock('foo'); | |
| t.equals(next, null); | |
| // Add a block with "foo" as its next. | |
| b.createBlock({ | |
| id: 'foo2', | |
| opcode: 'TEST_BLOCK', | |
| next: 'foo', | |
| fields: {}, | |
| inputs: {}, | |
| topLevel: true | |
| }); | |
| next = b.getNextBlock('foo2'); | |
| t.equals(next, 'foo'); | |
| // Block that doesn't exist. | |
| const noBlock = b.getNextBlock('?'); | |
| t.equals(noBlock, null); | |
| t.end(); | |
| }); | |
| test('getBranch', t => { | |
| const b = new Blocks(new Runtime()); | |
| // Single branch | |
| b.createBlock({ | |
| id: 'foo', | |
| opcode: 'TEST_BLOCK', | |
| next: null, | |
| fields: {}, | |
| inputs: { | |
| SUBSTACK: { | |
| name: 'SUBSTACK', | |
| block: 'foo2', | |
| shadow: null | |
| } | |
| }, | |
| topLevel: true | |
| }); | |
| b.createBlock({ | |
| id: 'foo2', | |
| opcode: 'TEST_BLOCK', | |
| next: null, | |
| fields: {}, | |
| inputs: {}, | |
| topLevel: false | |
| }); | |
| const branch = b.getBranch('foo'); | |
| t.equals(branch, 'foo2'); | |
| const notBranch = b.getBranch('?'); | |
| t.equals(notBranch, null); | |
| t.end(); | |
| }); | |
| test('getBranch2', t => { | |
| const b = new Blocks(new Runtime()); | |
| // Second branch | |
| b.createBlock({ | |
| id: 'foo', | |
| opcode: 'TEST_BLOCK', | |
| next: null, | |
| fields: {}, | |
| inputs: { | |
| SUBSTACK: { | |
| name: 'SUBSTACK', | |
| block: 'foo2', | |
| shadow: null | |
| }, | |
| SUBSTACK2: { | |
| name: 'SUBSTACK2', | |
| block: 'foo3', | |
| shadow: null | |
| } | |
| }, | |
| topLevel: true | |
| }); | |
| b.createBlock({ | |
| id: 'foo2', | |
| opcode: 'TEST_BLOCK', | |
| next: null, | |
| fields: {}, | |
| inputs: {}, | |
| topLevel: false | |
| }); | |
| b.createBlock({ | |
| id: 'foo3', | |
| opcode: 'TEST_BLOCK', | |
| next: null, | |
| fields: {}, | |
| inputs: {}, | |
| topLevel: false | |
| }); | |
| const branch1 = b.getBranch('foo', 1); | |
| const branch2 = b.getBranch('foo', 2); | |
| t.equals(branch1, 'foo2'); | |
| t.equals(branch2, 'foo3'); | |
| t.end(); | |
| }); | |
| test('getBranch with none', t => { | |
| const b = new Blocks(new Runtime()); | |
| b.createBlock({ | |
| id: 'foo', | |
| opcode: 'TEST_BLOCK', | |
| next: null, | |
| fields: {}, | |
| inputs: {}, | |
| topLevel: true | |
| }); | |
| const noBranch = b.getBranch('foo'); | |
| t.equals(noBranch, null); | |
| t.end(); | |
| }); | |
| test('getOpcode', t => { | |
| const b = new Blocks(new Runtime()); | |
| const block = { | |
| id: 'foo', | |
| opcode: 'TEST_BLOCK', | |
| next: null, | |
| fields: {}, | |
| inputs: {}, | |
| topLevel: true | |
| }; | |
| b.createBlock(block); | |
| const opcode = b.getOpcode(block); | |
| t.equals(opcode, 'TEST_BLOCK'); | |
| const undefinedBlock = b.getBlock('?'); | |
| const undefinedOpcode = b.getOpcode(undefinedBlock); | |
| t.equals(undefinedOpcode, null); | |
| t.end(); | |
| }); | |
| test('mutationToXML', t => { | |
| const b = new Blocks(new Runtime()); | |
| const testStringRaw = '"arbitrary" & \'complicated\' test string'; | |
| const testStringEscaped = '\\"arbitrary\\" & 'complicated' test string'; | |
| const mutation = { | |
| tagName: 'mutation', | |
| children: [], | |
| blockInfo: { | |
| text: testStringRaw | |
| } | |
| }; | |
| const xml = b.mutationToXML(mutation); | |
| t.equals( | |
| xml, | |
| `<mutation blockInfo="{"text":"${testStringEscaped}"}"></mutation>` | |
| ); | |
| t.end(); | |
| }); | |
| // Block events tests | |
| test('create', t => { | |
| const b = new Blocks(new Runtime()); | |
| b.createBlock({ | |
| id: 'foo', | |
| opcode: 'TEST_BLOCK', | |
| next: null, | |
| fields: {}, | |
| inputs: {}, | |
| topLevel: true | |
| }); | |
| t.type(b._blocks.foo, 'object'); | |
| t.equal(b._blocks.foo.opcode, 'TEST_BLOCK'); | |
| t.notEqual(b._scripts.indexOf('foo'), -1); | |
| t.end(); | |
| }); | |
| test('move', t => { | |
| const b = new Blocks(new Runtime()); | |
| b.createBlock({ | |
| id: 'foo', | |
| opcode: 'TEST_BLOCK', | |
| next: null, | |
| fields: {}, | |
| inputs: {}, | |
| topLevel: true | |
| }); | |
| b.createBlock({ | |
| id: 'bar', | |
| opcode: 'TEST_BLOCK', | |
| next: null, | |
| fields: {}, | |
| inputs: {}, | |
| topLevel: true | |
| }); | |
| // Attach 'bar' to the end of 'foo' | |
| b.moveBlock({ | |
| id: 'bar', | |
| newParent: 'foo' | |
| }); | |
| t.equal(b._scripts.length, 1); | |
| t.equal(Object.keys(b._blocks).length, 2); | |
| t.equal(b._blocks.foo.next, 'bar'); | |
| // Detach 'bar' from 'foo' | |
| b.moveBlock({ | |
| id: 'bar', | |
| oldParent: 'foo' | |
| }); | |
| t.equal(b._scripts.length, 2); | |
| t.equal(Object.keys(b._blocks).length, 2); | |
| t.equal(b._blocks.foo.next, null); | |
| t.end(); | |
| }); | |
| test('move into empty', t => { | |
| const b = new Blocks(new Runtime()); | |
| b.createBlock({ | |
| id: 'foo', | |
| opcode: 'TEST_BLOCK', | |
| next: null, | |
| fields: {}, | |
| inputs: {}, | |
| topLevel: true | |
| }); | |
| b.createBlock({ | |
| id: 'bar', | |
| opcode: 'TEST_BLOCK', | |
| next: null, | |
| fields: {}, | |
| inputs: {}, | |
| topLevel: true | |
| }); | |
| b.moveBlock({ | |
| id: 'bar', | |
| newInput: 'fooInput', | |
| newParent: 'foo' | |
| }); | |
| t.equal(b._blocks.foo.inputs.fooInput.block, 'bar'); | |
| t.end(); | |
| }); | |
| test('move no obscure shadow', t => { | |
| const b = new Blocks(new Runtime()); | |
| b.createBlock({ | |
| id: 'foo', | |
| opcode: 'TEST_BLOCK', | |
| next: null, | |
| fields: {}, | |
| inputs: { | |
| fooInput: { | |
| name: 'fooInput', | |
| block: 'x', | |
| shadow: 'y' | |
| } | |
| }, | |
| topLevel: true | |
| }); | |
| b.createBlock({ | |
| id: 'bar', | |
| opcode: 'TEST_BLOCK', | |
| next: null, | |
| fields: {}, | |
| inputs: {}, | |
| topLevel: true | |
| }); | |
| b.moveBlock({ | |
| id: 'bar', | |
| newInput: 'fooInput', | |
| newParent: 'foo' | |
| }); | |
| t.equal(b._blocks.foo.inputs.fooInput.block, 'bar'); | |
| t.equal(b._blocks.foo.inputs.fooInput.shadow, 'y'); | |
| t.end(); | |
| }); | |
| test('move - attaching new shadow', t => { | |
| const b = new Blocks(new Runtime()); | |
| // Block/shadow are null to mimic state right after a procedure_call block | |
| // is mutated by adding an input. The "move" will attach the new shadow. | |
| b.createBlock({ | |
| id: 'foo', | |
| opcode: 'TEST_BLOCK', | |
| next: null, | |
| fields: {}, | |
| inputs: { | |
| fooInput: { | |
| name: 'fooInput', | |
| block: null, | |
| shadow: null | |
| } | |
| }, | |
| topLevel: true | |
| }); | |
| b.createBlock({ | |
| id: 'bar', | |
| opcode: 'TEST_BLOCK', | |
| shadow: true, | |
| next: null, | |
| fields: {}, | |
| inputs: {}, | |
| topLevel: true | |
| }); | |
| b.moveBlock({ | |
| id: 'bar', | |
| newInput: 'fooInput', | |
| newParent: 'foo' | |
| }); | |
| t.equal(b._blocks.foo.inputs.fooInput.block, 'bar'); | |
| t.equal(b._blocks.foo.inputs.fooInput.shadow, 'bar'); | |
| t.end(); | |
| }); | |
| test('change', t => { | |
| const b = new Blocks(new Runtime()); | |
| b.createBlock({ | |
| id: 'foo', | |
| opcode: 'TEST_BLOCK', | |
| next: null, | |
| fields: { | |
| someField: { | |
| name: 'someField', | |
| value: 'initial-value' | |
| } | |
| }, | |
| inputs: {}, | |
| topLevel: true | |
| }); | |
| // Test that the field is updated | |
| t.equal(b._blocks.foo.fields.someField.value, 'initial-value'); | |
| b.changeBlock({ | |
| element: 'field', | |
| id: 'foo', | |
| name: 'someField', | |
| value: 'final-value' | |
| }); | |
| t.equal(b._blocks.foo.fields.someField.value, 'final-value'); | |
| // Invalid cases | |
| // No `element` | |
| b.changeBlock({ | |
| id: 'foo', | |
| name: 'someField', | |
| value: 'invalid-value' | |
| }); | |
| t.equal(b._blocks.foo.fields.someField.value, 'final-value'); | |
| // No block ID | |
| b.changeBlock({ | |
| element: 'field', | |
| name: 'someField', | |
| value: 'invalid-value' | |
| }); | |
| t.equal(b._blocks.foo.fields.someField.value, 'final-value'); | |
| // No such field | |
| b.changeBlock({ | |
| element: 'field', | |
| id: 'foo', | |
| name: 'someWrongField', | |
| value: 'final-value' | |
| }); | |
| t.equal(b._blocks.foo.fields.someField.value, 'final-value'); | |
| t.end(); | |
| }); | |
| test('delete', t => { | |
| const b = new Blocks(new Runtime()); | |
| b.createBlock({ | |
| id: 'foo', | |
| opcode: 'TEST_BLOCK', | |
| next: null, | |
| fields: {}, | |
| inputs: {}, | |
| topLevel: true | |
| }); | |
| b.deleteBlock('foo'); | |
| t.type(b._blocks.foo, 'undefined'); | |
| t.equal(b._scripts.indexOf('foo'), -1); | |
| t.end(); | |
| }); | |
| test('delete chain', t => { | |
| // Create a chain of connected blocks and delete the top one. | |
| // All of them should be deleted. | |
| const b = new Blocks(new Runtime()); | |
| b.createBlock({ | |
| id: 'foo', | |
| opcode: 'TEST_BLOCK', | |
| next: 'foo2', | |
| fields: {}, | |
| inputs: {}, | |
| topLevel: true | |
| }); | |
| b.createBlock({ | |
| id: 'foo2', | |
| opcode: 'TEST_BLOCK', | |
| next: 'foo3', | |
| fields: {}, | |
| inputs: {}, | |
| topLevel: false | |
| }); | |
| b.createBlock({ | |
| id: 'foo3', | |
| opcode: 'TEST_BLOCK', | |
| next: null, | |
| fields: {}, | |
| inputs: {}, | |
| topLevel: false | |
| }); | |
| b.deleteBlock('foo'); | |
| t.type(b._blocks.foo, 'undefined'); | |
| t.type(b._blocks.foo2, 'undefined'); | |
| t.type(b._blocks.foo3, 'undefined'); | |
| t.equal(b._scripts.indexOf('foo'), -1); | |
| t.equal(Object.keys(b._blocks).length, 0); | |
| t.equal(b._scripts.length, 0); | |
| t.end(); | |
| }); | |
| test('delete inputs', t => { | |
| // Create a block with two inputs, one of which has its own input. | |
| // Delete the block - all of them should be deleted. | |
| const b = new Blocks(new Runtime()); | |
| b.createBlock({ | |
| id: 'foo', | |
| opcode: 'TEST_BLOCK', | |
| next: null, | |
| fields: {}, | |
| inputs: { | |
| input1: { | |
| name: 'input1', | |
| block: 'foo2', | |
| shadow: 'foo2' | |
| }, | |
| SUBSTACK: { | |
| name: 'SUBSTACK', | |
| block: 'foo3', | |
| shadow: null | |
| } | |
| }, | |
| topLevel: true | |
| }); | |
| b.createBlock({ | |
| id: 'foo2', | |
| opcode: 'TEST_BLOCK', | |
| next: null, | |
| fields: {}, | |
| inputs: {}, | |
| topLevel: false | |
| }); | |
| b.createBlock({ | |
| id: 'foo5', | |
| opcode: 'TEST_OBSCURED_SHADOW', | |
| next: null, | |
| fields: {}, | |
| inputs: {}, | |
| topLevel: false | |
| }); | |
| b.createBlock({ | |
| id: 'foo3', | |
| opcode: 'TEST_BLOCK', | |
| next: null, | |
| fields: {}, | |
| inputs: { | |
| subinput: { | |
| name: 'subinput', | |
| block: 'foo4', | |
| shadow: 'foo5' | |
| } | |
| }, | |
| topLevel: false | |
| }); | |
| b.createBlock({ | |
| id: 'foo4', | |
| opcode: 'TEST_BLOCK', | |
| next: null, | |
| fields: {}, | |
| inputs: {}, | |
| topLevel: false | |
| }); | |
| b.deleteBlock('foo'); | |
| t.type(b._blocks.foo, 'undefined'); | |
| t.type(b._blocks.foo2, 'undefined'); | |
| t.type(b._blocks.foo3, 'undefined'); | |
| t.type(b._blocks.foo4, 'undefined'); | |
| t.type(b._blocks.foo5, 'undefined'); | |
| t.equal(b._scripts.indexOf('foo'), -1); | |
| t.equal(Object.keys(b._blocks).length, 0); | |
| t.equal(b._scripts.length, 0); | |
| t.end(); | |
| }); | |
| test('updateAssetName function updates name in sound field', t => { | |
| const b = new Blocks(new Runtime()); | |
| b.createBlock({ | |
| id: 'foo', | |
| fields: { | |
| SOUND_MENU: { | |
| name: 'SOUND_MENU', | |
| value: 'name1' | |
| } | |
| } | |
| }); | |
| t.equals(b.getBlock('foo').fields.SOUND_MENU.value, 'name1'); | |
| b.updateAssetName('name1', 'name2', 'sound'); | |
| t.equals(b.getBlock('foo').fields.SOUND_MENU.value, 'name2'); | |
| t.end(); | |
| }); | |
| test('updateAssetName function updates name in costume field', t => { | |
| const b = new Blocks(new Runtime()); | |
| b.createBlock({ | |
| id: 'foo', | |
| fields: { | |
| COSTUME: { | |
| name: 'COSTUME', | |
| value: 'name1' | |
| } | |
| } | |
| }); | |
| t.equals(b.getBlock('foo').fields.COSTUME.value, 'name1'); | |
| b.updateAssetName('name1', 'name2', 'costume'); | |
| t.equals(b.getBlock('foo').fields.COSTUME.value, 'name2'); | |
| t.end(); | |
| }); | |
| test('updateAssetName function updates name in backdrop field', t => { | |
| const b = new Blocks(new Runtime()); | |
| b.createBlock({ | |
| id: 'foo', | |
| fields: { | |
| BACKDROP: { | |
| name: 'BACKDROP', | |
| value: 'name1' | |
| } | |
| } | |
| }); | |
| t.equals(b.getBlock('foo').fields.BACKDROP.value, 'name1'); | |
| b.updateAssetName('name1', 'name2', 'backdrop'); | |
| t.equals(b.getBlock('foo').fields.BACKDROP.value, 'name2'); | |
| t.end(); | |
| }); | |
| test('updateAssetName function updates name in all sprite fields', t => { | |
| const b = new Blocks(new Runtime()); | |
| b.createBlock({ | |
| id: 'id1', | |
| fields: { | |
| TOWARDS: { | |
| name: 'TOWARDS', | |
| value: 'name1' | |
| } | |
| } | |
| }); | |
| b.createBlock({ | |
| id: 'id2', | |
| fields: { | |
| TO: { | |
| name: 'TO', | |
| value: 'name1' | |
| } | |
| } | |
| }); | |
| b.createBlock({ | |
| id: 'id3', | |
| fields: { | |
| OBJECT: { | |
| name: 'OBJECT', | |
| value: 'name1' | |
| } | |
| } | |
| }); | |
| b.createBlock({ | |
| id: 'id4', | |
| fields: { | |
| VIDEOONMENU2: { | |
| name: 'VIDEOONMENU2', | |
| value: 'name1' | |
| } | |
| } | |
| }); | |
| b.createBlock({ | |
| id: 'id5', | |
| fields: { | |
| DISTANCETOMENU: { | |
| name: 'DISTANCETOMENU', | |
| value: 'name1' | |
| } | |
| } | |
| }); | |
| b.createBlock({ | |
| id: 'id6', | |
| fields: { | |
| TOUCHINGOBJECTMENU: { | |
| name: 'TOUCHINGOBJECTMENU', | |
| value: 'name1' | |
| } | |
| } | |
| }); | |
| b.createBlock({ | |
| id: 'id7', | |
| fields: { | |
| CLONE_OPTION: { | |
| name: 'CLONE_OPTION', | |
| value: 'name1' | |
| } | |
| } | |
| }); | |
| t.equals(b.getBlock('id1').fields.TOWARDS.value, 'name1'); | |
| t.equals(b.getBlock('id2').fields.TO.value, 'name1'); | |
| t.equals(b.getBlock('id3').fields.OBJECT.value, 'name1'); | |
| t.equals(b.getBlock('id4').fields.VIDEOONMENU2.value, 'name1'); | |
| t.equals(b.getBlock('id5').fields.DISTANCETOMENU.value, 'name1'); | |
| t.equals(b.getBlock('id6').fields.TOUCHINGOBJECTMENU.value, 'name1'); | |
| t.equals(b.getBlock('id7').fields.CLONE_OPTION.value, 'name1'); | |
| b.updateAssetName('name1', 'name2', 'sprite'); | |
| t.equals(b.getBlock('id1').fields.TOWARDS.value, 'name2'); | |
| t.equals(b.getBlock('id2').fields.TO.value, 'name2'); | |
| t.equals(b.getBlock('id3').fields.OBJECT.value, 'name2'); | |
| t.equals(b.getBlock('id4').fields.VIDEOONMENU2.value, 'name2'); | |
| t.equals(b.getBlock('id5').fields.DISTANCETOMENU.value, 'name2'); | |
| t.equals(b.getBlock('id6').fields.TOUCHINGOBJECTMENU.value, 'name2'); | |
| t.equals(b.getBlock('id7').fields.CLONE_OPTION.value, 'name2'); | |
| t.end(); | |
| }); | |
| test('updateAssetName function updates name according to asset type', t => { | |
| const b = new Blocks(new Runtime()); | |
| b.createBlock({ | |
| id: 'id1', | |
| fields: { | |
| SOUND_MENU: { | |
| name: 'SOUND_MENU', | |
| value: 'name1' | |
| } | |
| } | |
| }); | |
| b.createBlock({ | |
| id: 'id2', | |
| fields: { | |
| COSTUME: { | |
| name: 'COSTUME', | |
| value: 'name1' | |
| } | |
| } | |
| }); | |
| t.equals(b.getBlock('id1').fields.SOUND_MENU.value, 'name1'); | |
| t.equals(b.getBlock('id2').fields.COSTUME.value, 'name1'); | |
| b.updateAssetName('name1', 'name2', 'sound'); | |
| // only sound should get renamed | |
| t.equals(b.getBlock('id1').fields.SOUND_MENU.value, 'name2'); | |
| t.equals(b.getBlock('id2').fields.COSTUME.value, 'name1'); | |
| t.end(); | |
| }); | |
| test('updateAssetName only updates given name', t => { | |
| const b = new Blocks(new Runtime()); | |
| b.createBlock({ | |
| id: 'id1', | |
| fields: { | |
| COSTUME: { | |
| name: 'COSTUME', | |
| value: 'name1' | |
| } | |
| } | |
| }); | |
| b.createBlock({ | |
| id: 'id2', | |
| fields: { | |
| COSTUME: { | |
| name: 'COSTUME', | |
| value: 'foo' | |
| } | |
| } | |
| }); | |
| t.equals(b.getBlock('id1').fields.COSTUME.value, 'name1'); | |
| t.equals(b.getBlock('id2').fields.COSTUME.value, 'foo'); | |
| b.updateAssetName('name1', 'name2', 'costume'); | |
| t.equals(b.getBlock('id1').fields.COSTUME.value, 'name2'); | |
| t.equals(b.getBlock('id2').fields.COSTUME.value, 'foo'); | |
| t.end(); | |
| }); | |
| test('updateAssetName doesn\'t update name if name isn\'t being used', t => { | |
| const b = new Blocks(new Runtime()); | |
| b.createBlock({ | |
| id: 'id1', | |
| fields: { | |
| BACKDROP: { | |
| name: 'BACKDROP', | |
| value: 'foo' | |
| } | |
| } | |
| }); | |
| t.equals(b.getBlock('id1').fields.BACKDROP.value, 'foo'); | |
| b.updateAssetName('name1', 'name2', 'backdrop'); | |
| t.equals(b.getBlock('id1').fields.BACKDROP.value, 'foo'); | |
| t.end(); | |
| }); | |
| test('updateSensingOfReference renames variables in sensing_of block', t => { | |
| const b = new Blocks(new Runtime()); | |
| b.createBlock({ | |
| id: 'id1', | |
| opcode: 'sensing_of', | |
| fields: { | |
| PROPERTY: { | |
| name: 'PROPERTY', | |
| value: 'foo' | |
| } | |
| }, | |
| inputs: { | |
| OBJECT: { | |
| name: 'OBJECT', | |
| block: 'id2', | |
| shadow: 'id2' | |
| } | |
| } | |
| }); | |
| b.createBlock({ | |
| id: 'id2', | |
| fields: { | |
| OBJECT: { | |
| name: 'OBJECT', | |
| value: '_stage_' | |
| } | |
| } | |
| }); | |
| t.equals(b.getBlock('id1').fields.PROPERTY.value, 'foo'); | |
| b.updateSensingOfReference('foo', 'bar', '_stage_'); | |
| t.equals(b.getBlock('id1').fields.PROPERTY.value, 'bar'); | |
| t.end(); | |
| }); | |
| test('updateSensingOfReference doesn\'t rename if block is inserted', t => { | |
| const b = new Blocks(new Runtime()); | |
| b.createBlock({ | |
| id: 'id1', | |
| opcode: 'sensing_of', | |
| fields: { | |
| PROPERTY: { | |
| name: 'PROPERTY', | |
| value: 'foo' | |
| } | |
| }, | |
| inputs: { | |
| OBJECT: { | |
| name: 'OBJECT', | |
| block: 'id3', | |
| shadow: 'id2' | |
| } | |
| } | |
| }); | |
| b.createBlock({ | |
| id: 'id2', | |
| fields: { | |
| OBJECT: { | |
| name: 'OBJECT', | |
| value: '_stage_' | |
| } | |
| } | |
| }); | |
| b.createBlock({ | |
| id: 'id3', | |
| opcode: 'answer' | |
| }); | |
| t.equals(b.getBlock('id1').fields.PROPERTY.value, 'foo'); | |
| b.updateSensingOfReference('foo', 'bar', '_stage_'); | |
| t.equals(b.getBlock('id1').fields.PROPERTY.value, 'foo'); | |
| t.end(); | |
| }); | |
| test('updateSensingOfReference doesn\'t rename if name is not being used', t => { | |
| const b = new Blocks(new Runtime()); | |
| b.createBlock({ | |
| id: 'id1', | |
| opcode: 'sensing_of', | |
| fields: { | |
| PROPERTY: { | |
| name: 'PROPERTY', | |
| value: 'foo' | |
| } | |
| }, | |
| inputs: { | |
| OBJECT: { | |
| name: 'OBJECT', | |
| block: 'id2', | |
| shadow: 'id2' | |
| } | |
| } | |
| }); | |
| b.createBlock({ | |
| id: 'id2', | |
| fields: { | |
| OBJECT: { | |
| name: 'OBJECT', | |
| value: '_stage_' | |
| } | |
| } | |
| }); | |
| t.equals(b.getBlock('id1').fields.PROPERTY.value, 'foo'); | |
| b.updateSensingOfReference('meow', 'meow2', '_stage_'); | |
| t.equals(b.getBlock('id1').fields.PROPERTY.value, 'foo'); | |
| t.end(); | |
| }); | |
| test('updateSensingOfReference doesn\'t rename other targets\' variables', t => { | |
| const b = new Blocks(new Runtime()); | |
| b.createBlock({ | |
| id: 'id1', | |
| opcode: 'sensing_of', | |
| fields: { | |
| PROPERTY: { | |
| name: 'PROPERTY', | |
| value: 'foo' | |
| } | |
| }, | |
| inputs: { | |
| OBJECT: { | |
| name: 'OBJECT', | |
| block: 'id2', | |
| shadow: 'id2' | |
| } | |
| } | |
| }); | |
| b.createBlock({ | |
| id: 'id2', | |
| fields: { | |
| OBJECT: { | |
| name: 'OBJECT', | |
| value: '_stage_' | |
| } | |
| } | |
| }); | |
| t.equals(b.getBlock('id1').fields.PROPERTY.value, 'foo'); | |
| b.updateSensingOfReference('foo', 'bar', 'Cat'); | |
| t.equals(b.getBlock('id1').fields.PROPERTY.value, 'foo'); | |
| t.end(); | |
| }); | |
| test('updateTargetSpecificBlocks changes sprite clicked hat to stage clicked for stage', t => { | |
| const b = new Blocks(new Runtime()); | |
| b.createBlock({ | |
| id: 'originallySpriteClicked', | |
| opcode: 'event_whenthisspriteclicked' | |
| }); | |
| b.createBlock({ | |
| id: 'originallyStageClicked', | |
| opcode: 'event_whenstageclicked' | |
| }); | |
| // originallySpriteClicked does not update when on a non-stage target | |
| b.updateTargetSpecificBlocks(false /* isStage */); | |
| t.equals(b.getBlock('originallySpriteClicked').opcode, 'event_whenthisspriteclicked'); | |
| // originallySpriteClicked does update when on a stage target | |
| b.updateTargetSpecificBlocks(true /* isStage */); | |
| t.equals(b.getBlock('originallySpriteClicked').opcode, 'event_whenstageclicked'); | |
| // originallyStageClicked does not update when on a stage target | |
| b.updateTargetSpecificBlocks(true /* isStage */); | |
| t.equals(b.getBlock('originallyStageClicked').opcode, 'event_whenstageclicked'); | |
| // originallyStageClicked does update when on a non-stage target | |
| b.updateTargetSpecificBlocks(false/* isStage */); | |
| t.equals(b.getBlock('originallyStageClicked').opcode, 'event_whenthisspriteclicked'); | |
| t.end(); | |
| }); | |
| test('getAllVariableAndListReferences returns an empty map references when variable blocks do not exist', t => { | |
| const b = new Blocks(new Runtime()); | |
| t.equal(Object.keys(b.getAllVariableAndListReferences()).length, 0); | |
| t.end(); | |
| }); | |
| test('getAllVariableAndListReferences returns references when variable blocks exist', t => { | |
| const b = new Blocks(new Runtime()); | |
| let varListRefs = b.getAllVariableAndListReferences(); | |
| t.equal(Object.keys(varListRefs).length, 0); | |
| b.createBlock(adapter(events.mockVariableBlock)[0]); | |
| b.createBlock(adapter(events.mockListBlock)[0]); | |
| varListRefs = b.getAllVariableAndListReferences(); | |
| 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 list id']), true); | |
| t.equal(varListRefs['mock list id'].length, 1); | |
| t.equal(varListRefs['mock list id'][0].type, Variable.LIST_TYPE); | |
| t.equal(varListRefs['mock list id'][0].referencingField.value, 'a mock list'); | |
| 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(); | |
| }); | |