diff --git a/packages/scratch-gui/src/lib/ruby-generator/data.js b/packages/scratch-gui/src/lib/ruby-generator/data.js index 3ddcf181e54..0f0b3da9652 100644 --- a/packages/scratch-gui/src/lib/ruby-generator/data.js +++ b/packages/scratch-gui/src/lib/ruby-generator/data.js @@ -134,6 +134,13 @@ export default function (Generator) { // === Smalruby: Start of array syntax === const getListName = function (block) { + const comment = Generator.getCommentText(block); + if (comment) { + const lvarMatch = comment.match(/@ruby:lvar:([^:,\s]+)/); + if (lvarMatch) { + return lvarMatch[1]; + } + } return Generator.listName(Generator.getFieldId(block, 'LIST')); }; @@ -150,7 +157,7 @@ export default function (Generator) { const indexBlock = Generator.getBlock(indexBlockId); if (indexBlock && indexBlock.opcode === 'operator_add') { const comment = Generator.getCommentText(indexBlock); - if (comment && comment.includes('@ruby:array:index_offset')) { + if (comment && comment.includes('@ruby:array:index')) { // Use NUM1 directly (the original 0-indexed value) return Generator.valueToCode(indexBlock, 'NUM1', Generator.ORDER_NONE) || 0; } @@ -198,8 +205,9 @@ export default function (Generator) { // === Smalruby: Start of array syntax === const comment = Generator.getCommentText(block); - if (comment && comment.startsWith('@ruby:array:literal:')) { - const count = parseInt(comment.split(':')[3], 10); + const arrayLiteralMatch = comment ? comment.match(/@ruby:array:literal:(\d+)/) : null; + if (arrayLiteralMatch) { + const count = parseInt(arrayLiteralMatch[1], 10); const values = []; let nextId = block.next; for (let i = 0; i < count; i++) { diff --git a/packages/scratch-gui/src/lib/ruby-to-blocks-converter/variables.js b/packages/scratch-gui/src/lib/ruby-to-blocks-converter/variables.js index c3804729f5e..b0fa773592b 100644 --- a/packages/scratch-gui/src/lib/ruby-to-blocks-converter/variables.js +++ b/packages/scratch-gui/src/lib/ruby-to-blocks-converter/variables.js @@ -66,6 +66,8 @@ const VariablesConverter = { prefixedName = `$${varName}`; } else if (variable.scope === 'instance') { prefixedName = `@${varName}`; + } else if (variable.scope === 'local') { + prefixedName = variable.originalName; } else { return {block: null, converted: false}; } @@ -87,18 +89,16 @@ const VariablesConverter = { /** * Adjust a 0-indexed Ruby array index to 1-indexed Scratch list index. - * Only adjusts when the receiver was converted from variable to list (array syntax). + * Always wraps in operator_add(index, 1) with @ruby:array:index comment + * to enable round-trip conversion. */ const adjustIndex = function (index, converted) { if (!converted) return index; - if (typeof index === 'number') return index + 1; - if (converter._isPrimitive(index) && - (index.type === 'int' || index.type === 'float')) { - return index.value + 1; - } - // For block expressions, wrap in operator_add(index, 1) with comment - // This is handled at the generator level via @ruby:array:index_offset - return index; + const addBlock = converter._createBlock('operator_add', 'value'); + converter._addNumberInput(addBlock, 'NUM1', 'math_number', index, 0); + converter._addNumberInput(addBlock, 'NUM2', 'math_number', 1, 0); + addBlock.comment = converter._createComment('@ruby:array:index', addBlock.id); + return addBlock; }; // === Smalruby: End of array syntax === @@ -632,7 +632,8 @@ const VariablesConverter = { converter.registerOnVasgn((scope, variable, rh) => { // === Smalruby: Start of array syntax === - if ((scope === 'global' || scope === 'instance') && + if ((scope === 'global' || scope === 'instance' || + (scope === 'local' && !variable.isArgument)) && converter._isArray(rh)) { if (converter.version < 2) { throw new RubyToBlocksConverterError( @@ -644,8 +645,10 @@ const VariablesConverter = { let prefixedName; if (variable.scope === 'global') { prefixedName = `$${variable.name}`; - } else { + } else if (variable.scope === 'instance') { prefixedName = `@${variable.name}`; + } else { + prefixedName = variable.originalName; } const listVar = converter._lookupOrCreateList(prefixedName); @@ -660,8 +663,13 @@ const VariablesConverter = { } } }); + let arrayLiteralComment = `@ruby:array:literal:${elements.length}`; + if (scope === 'local') { + arrayLiteralComment = + `@ruby:lvar:${variable.originalName}:${variable.scopeIndex},${arrayLiteralComment}`; + } clearBlock.comment = converter._createComment( - `@ruby:array:literal:${elements.length}`, clearBlock.id + arrayLiteralComment, clearBlock.id ); // Create push blocks for each element diff --git a/packages/scratch-gui/test/unit/lib/ruby-roundtrip/variables-v2-array.test.js b/packages/scratch-gui/test/unit/lib/ruby-roundtrip/variables-v2-array.test.js new file mode 100644 index 00000000000..6c534a4e810 --- /dev/null +++ b/packages/scratch-gui/test/unit/lib/ruby-roundtrip/variables-v2-array.test.js @@ -0,0 +1,140 @@ +// === Smalruby: This file is Smalruby-specific (v2 array syntax roundtrip tests) === +/** + * V2 array syntax roundtrip tests. + * Verifies that Ruby → Blocks → Ruby produces correct output + * for array operations using the operator_add(index, 1) pattern. + */ +import dedent from 'dedent'; +import { + makeSpriteTarget, + makeConverter, + setupRubyGenerator, + expectRoundTrip +} from '../../helpers/ruby-roundtrip-helper'; + +describe('Ruby Roundtrip: V2 array syntax', () => { + let target, runtime, converter; + + beforeEach(() => { + ({target, runtime} = makeSpriteTarget()); + setupRubyGenerator(); + converter = makeConverter(target, runtime, {version: '2'}); + }); + + test('global array literal and indexing', async () => { + await expectRoundTrip(converter, target, dedent` + $a = ["a", "b"] + say($a[0]) + `); + }); + + test('instance array literal and indexing', async () => { + await expectRoundTrip(converter, target, dedent` + @a = ["a", "b"] + say(@a[0]) + `); + }); + + test('array indexing with literal index', async () => { + await expectRoundTrip(converter, target, dedent` + $a = ["x", "y", "z"] + say($a[2]) + `); + }); + + test('array indexing with variable index', async () => { + await expectRoundTrip(converter, target, dedent` + @a = ["a", "b"] + @b = 1 + say(@a[@b]) + `); + }); + + test('array delete_at with literal index', async () => { + await expectRoundTrip(converter, target, dedent` + $a = ["a", "b", "c"] + $a.delete_at(0) + `); + }); + + test('array insert with literal index', async () => { + await expectRoundTrip(converter, target, dedent` + $a = ["a", "b"] + $a.insert(0, "x") + `); + }); + + test('array replace with literal index', async () => { + await expectRoundTrip(converter, target, dedent` + $a = ["a", "b"] + $a[0] = "x" + `); + }); + + test('array push', async () => { + await expectRoundTrip(converter, target, dedent` + $a = ["a"] + $a.push("b") + `); + }); + + test('array push with << operator', async () => { + await expectRoundTrip(converter, target, dedent` + $a = ["a"] + $a << "b" + `, dedent` + $a = ["a"] + $a.push("b") + `); + }); + + test('array clear', async () => { + await expectRoundTrip(converter, target, dedent` + $a = ["a", "b"] + $a.clear + `); + }); + + test('array index method', async () => { + await expectRoundTrip(converter, target, dedent` + $a = ["a", "b"] + + $a.index("b") + `, dedent` + $a = ["a", "b"] + + ($a.index("b")) + `); + }); + + test('array length', async () => { + await expectRoundTrip(converter, target, dedent` + $a = ["a", "b"] + + $a.length + `); + }); + + test('array include?', async () => { + await expectRoundTrip(converter, target, dedent` + $a = ["a", "b"] + + $a.include?("a") + `); + }); + + test('empty array literal', async () => { + await expectRoundTrip(converter, target, dedent` + $a = [] + `); + }); + + test('mixed array operations', async () => { + await expectRoundTrip(converter, target, dedent` + @a = ["a", "b"] + say(@a[0]) + @b = 1 + say(@a[@b]) + `); + }); +}); diff --git a/packages/scratch-gui/test/unit/lib/ruby-to-blocks-converter/variables/variables-array-global.test.js b/packages/scratch-gui/test/unit/lib/ruby-to-blocks-converter/variables/variables-array-global.test.js index e4f68c08d60..eedad91c9ca 100644 --- a/packages/scratch-gui/test/unit/lib/ruby-to-blocks-converter/variables/variables-array-global.test.js +++ b/packages/scratch-gui/test/unit/lib/ruby-to-blocks-converter/variables/variables-array-global.test.js @@ -75,7 +75,21 @@ describe('RubyToBlocksConverter/Variables/ArraySyntax', () => { inputs: [ { name: 'INDEX', - block: expectedInfo.makeNumber(1, 'math_integer') + block: { + opcode: 'operator_add', + comment: {text: '@ruby:array:index', minimized: true}, + inputs: [ + { + name: 'NUM1', + block: expectedInfo.makeNumber(0) + }, + { + name: 'NUM2', + block: expectedInfo.makeNumber(1) + } + ] + }, + shadow: expectedInfo.makeNumber(1, 'math_integer') } ] } @@ -113,7 +127,21 @@ describe('RubyToBlocksConverter/Variables/ArraySyntax', () => { inputs: [ { name: 'INDEX', - block: expectedInfo.makeNumber(1, 'math_integer') + block: { + opcode: 'operator_add', + comment: {text: '@ruby:array:index', minimized: true}, + inputs: [ + { + name: 'NUM1', + block: expectedInfo.makeNumber(0) + }, + { + name: 'NUM2', + block: expectedInfo.makeNumber(1) + } + ] + }, + shadow: expectedInfo.makeNumber(1, 'math_integer') }, { name: 'ITEM', @@ -139,7 +167,21 @@ describe('RubyToBlocksConverter/Variables/ArraySyntax', () => { inputs: [ { name: 'INDEX', - block: expectedInfo.makeNumber(1, 'math_integer') + block: { + opcode: 'operator_add', + comment: {text: '@ruby:array:index', minimized: true}, + inputs: [ + { + name: 'NUM1', + block: expectedInfo.makeNumber(0) + }, + { + name: 'NUM2', + block: expectedInfo.makeNumber(1) + } + ] + }, + shadow: expectedInfo.makeNumber(1, 'math_integer') }, { name: 'ITEM', @@ -165,7 +207,21 @@ describe('RubyToBlocksConverter/Variables/ArraySyntax', () => { inputs: [ { name: 'INDEX', - block: expectedInfo.makeNumber(1, 'math_integer') + block: { + opcode: 'operator_add', + comment: {text: '@ruby:array:index', minimized: true}, + inputs: [ + { + name: 'NUM1', + block: expectedInfo.makeNumber(0) + }, + { + name: 'NUM2', + block: expectedInfo.makeNumber(1) + } + ] + }, + shadow: expectedInfo.makeNumber(1, 'math_integer') } ] } diff --git a/packages/scratch-gui/test/unit/lib/ruby-to-blocks-converter/variables/variables-array-local.test.js b/packages/scratch-gui/test/unit/lib/ruby-to-blocks-converter/variables/variables-array-local.test.js new file mode 100644 index 00000000000..1fe35782241 --- /dev/null +++ b/packages/scratch-gui/test/unit/lib/ruby-to-blocks-converter/variables/variables-array-local.test.js @@ -0,0 +1,125 @@ +// === Smalruby: This file is Smalruby-specific (Ruby array syntax for local variable lists) === +import RubyToBlocksConverter from '../../../../../src/lib/ruby-to-blocks-converter'; +import { + convertAndExpectToEqualBlocks, + expectedInfo +} from '../../../../helpers/expect-to-equal-blocks'; + +describe('RubyToBlocksConverter/Variables/ArraySyntax/Local', () => { + let converter; + let target; + + beforeEach(() => { + converter = new RubyToBlocksConverter(null, {version: '2'}); + target = null; + }); + + describe('a - local variable array', () => { + test('array literal a = ["a", "b"] generates clear + push blocks', async () => { + const code = 'a = ["a", "b"]'; + const res = await converter.targetCodeToBlocks(target, code); + expect(converter.errors).toHaveLength(0); + expect(res).toBeTruthy(); + + // Verify blocks were created (clear + 2 push blocks) + const blockIds = Object.keys(converter.blocks); + const blocks = blockIds.map(id => converter.blocks[id]); + const clearBlock = blocks.find(b => b.opcode === 'data_deletealloflist'); + expect(clearBlock).toBeTruthy(); + + const pushBlocks = blocks.filter(b => b.opcode === 'data_addtolist'); + expect(pushBlocks).toHaveLength(2); + + // Verify clear block comment includes both lvar and array literal info + const clearComment = converter._context.comments[clearBlock.comment]; + expect(clearComment.text).toMatch(/@ruby:lvar:a:\d+/); + expect(clearComment.text).toMatch(/@ruby:array:literal:2/); + }); + + test('a[0] with array literal creates data_itemoflist', async () => { + const code = ` + a = ["a", "b"] + say(a[0]) + `; + const res = await converter.targetCodeToBlocks(target, code); + expect(converter.errors).toHaveLength(0); + expect(res).toBeTruthy(); + + // Verify data_itemoflist block was created + const blockIds = Object.keys(converter.blocks); + const blocks = blockIds.map(id => converter.blocks[id]); + const itemBlock = blocks.find(b => b.opcode === 'data_itemoflist'); + expect(itemBlock).toBeTruthy(); + + // Verify the block has lvar comment + const itemComment = converter._context.comments[itemBlock.comment]; + expect(itemComment.text).toMatch(/@ruby:lvar:a:\d+/); + }); + + test('a[variable_index] wraps in operator_add', async () => { + const code = ` + a = ["a", "b"] + @b = 1 + say(a[@b]) + `; + const res = await converter.targetCodeToBlocks(target, code); + expect(converter.errors).toHaveLength(0); + expect(res).toBeTruthy(); + + // Verify operator_add was created for the index + const blockIds = Object.keys(converter.blocks); + const blocks = blockIds.map(id => converter.blocks[id]); + const addBlock = blocks.find(b => b.opcode === 'operator_add'); + expect(addBlock).toBeTruthy(); + + const addComment = converter._context.comments[addBlock.comment]; + expect(addComment.text).toBe('@ruby:array:index'); + }); + + test('a.push("c") works with local variable', async () => { + const code = ` + a = ["a", "b"] + a.push("c") + `; + const res = await converter.targetCodeToBlocks(target, code); + expect(converter.errors).toHaveLength(0); + expect(res).toBeTruthy(); + + const blockIds = Object.keys(converter.blocks); + const blocks = blockIds.map(id => converter.blocks[id]); + // 2 push from literal + 1 standalone push + const pushBlocks = blocks.filter(b => b.opcode === 'data_addtolist'); + expect(pushBlocks).toHaveLength(3); + }); + + test('a.length works with local variable', async () => { + const code = ` + a = ["a", "b"] + say(a.length) + `; + const res = await converter.targetCodeToBlocks(target, code); + expect(converter.errors).toHaveLength(0); + expect(res).toBeTruthy(); + + const blockIds = Object.keys(converter.blocks); + const blocks = blockIds.map(id => converter.blocks[id]); + const lengthBlock = blocks.find(b => b.opcode === 'data_lengthoflist'); + expect(lengthBlock).toBeTruthy(); + }); + + test('empty array literal a = [] generates clear only', async () => { + const code = 'a = []'; + const res = await converter.targetCodeToBlocks(target, code); + expect(converter.errors).toHaveLength(0); + expect(res).toBeTruthy(); + + const blockIds = Object.keys(converter.blocks); + const blocks = blockIds.map(id => converter.blocks[id]); + const clearBlock = blocks.find(b => b.opcode === 'data_deletealloflist'); + expect(clearBlock).toBeTruthy(); + + const pushBlocks = blocks.filter(b => b.opcode === 'data_addtolist'); + expect(pushBlocks).toHaveLength(0); + }); + }); +}); diff --git a/packages/scratch-gui/test/unit/lib/ruby-to-blocks-converter/variables/variables-global1.test.js b/packages/scratch-gui/test/unit/lib/ruby-to-blocks-converter/variables/variables-global1.test.js index 28a48a371d8..1aebc477a01 100644 --- a/packages/scratch-gui/test/unit/lib/ruby-to-blocks-converter/variables/variables-global1.test.js +++ b/packages/scratch-gui/test/unit/lib/ruby-to-blocks-converter/variables/variables-global1.test.js @@ -65,7 +65,21 @@ describe('RubyToBlocksConverter/Variables', () => { inputs: [ { name: 'INDEX', - block: expectedInfo.makeNumber(1, 'math_integer') + block: { + opcode: 'operator_add', + comment: {text: '@ruby:array:index', minimized: true}, + inputs: [ + { + name: 'NUM1', + block: expectedInfo.makeNumber(0) + }, + { + name: 'NUM2', + block: expectedInfo.makeNumber(1) + } + ] + }, + shadow: expectedInfo.makeNumber(1, 'math_integer') } ] } diff --git a/packages/scratch-gui/test/unit/lib/ruby-to-blocks-converter/variables/variables-instance1.test.js b/packages/scratch-gui/test/unit/lib/ruby-to-blocks-converter/variables/variables-instance1.test.js index 3c88f6f8420..c6648ee74c6 100644 --- a/packages/scratch-gui/test/unit/lib/ruby-to-blocks-converter/variables/variables-instance1.test.js +++ b/packages/scratch-gui/test/unit/lib/ruby-to-blocks-converter/variables/variables-instance1.test.js @@ -65,7 +65,21 @@ describe('RubyToBlocksConverter/Variables', () => { inputs: [ { name: 'INDEX', - block: expectedInfo.makeNumber(1, 'math_integer') + block: { + opcode: 'operator_add', + comment: {text: '@ruby:array:index', minimized: true}, + inputs: [ + { + name: 'NUM1', + block: expectedInfo.makeNumber(0) + }, + { + name: 'NUM2', + block: expectedInfo.makeNumber(1) + } + ] + }, + shadow: expectedInfo.makeNumber(1, 'math_integer') } ] } diff --git a/packages/scratch-gui/test/unit/lib/ruby-to-blocks-converter/variables/variables-list-v1.test.js b/packages/scratch-gui/test/unit/lib/ruby-to-blocks-converter/variables/variables-list-v1.test.js new file mode 100644 index 00000000000..6c9c2ee8fec --- /dev/null +++ b/packages/scratch-gui/test/unit/lib/ruby-to-blocks-converter/variables/variables-list-v1.test.js @@ -0,0 +1,315 @@ +// === Smalruby: This file is Smalruby-specific (v1 list() syntax converter tests) === +import RubyToBlocksConverter from '../../../../../src/lib/ruby-to-blocks-converter'; +import { + convertAndExpectToEqualBlocks, + expectedInfo +} from '../../../../helpers/expect-to-equal-blocks'; + +describe('RubyToBlocksConverter/Variables/ListV1', () => { + let converter; + let target; + + beforeEach(() => { + // v1 converter (no version option = v1 default) + converter = new RubyToBlocksConverter(null); + target = null; + }); + + describe('list("$a") - v1 global list operations', () => { + const listName = '$a'; + + test('data_addtolist via push', async () => { + const code = `list("${listName}").push("thing")`; + const expected = [ + { + opcode: 'data_addtolist', + fields: [ + { + name: 'LIST', + list: listName + } + ], + inputs: [ + { + name: 'ITEM', + block: expectedInfo.makeText('thing') + } + ] + } + ]; + await convertAndExpectToEqualBlocks(converter, target, code, expected); + }); + + test('data_deleteoflist with 1-indexed (no adjustment)', async () => { + const code = `list("${listName}").delete_at(1)`; + const expected = [ + { + opcode: 'data_deleteoflist', + fields: [ + { + name: 'LIST', + list: listName + } + ], + inputs: [ + { + name: 'INDEX', + block: expectedInfo.makeNumber(1, 'math_integer') + } + ] + } + ]; + await convertAndExpectToEqualBlocks(converter, target, code, expected); + }); + + test('data_deletealloflist via clear', async () => { + const code = `list("${listName}").clear`; + const expected = [ + { + opcode: 'data_deletealloflist', + fields: [ + { + name: 'LIST', + list: listName + } + ] + } + ]; + await convertAndExpectToEqualBlocks(converter, target, code, expected); + }); + + test('data_insertatlist with 1-indexed (no adjustment)', async () => { + const code = `list("${listName}").insert(1, "thing")`; + const expected = [ + { + opcode: 'data_insertatlist', + fields: [ + { + name: 'LIST', + list: listName + } + ], + inputs: [ + { + name: 'INDEX', + block: expectedInfo.makeNumber(1, 'math_integer') + }, + { + name: 'ITEM', + block: expectedInfo.makeText('thing') + } + ] + } + ]; + await convertAndExpectToEqualBlocks(converter, target, code, expected); + }); + + test('data_replaceitemoflist with 1-indexed (no adjustment)', async () => { + const code = `list("${listName}")[1] = "thing"`; + const expected = [ + { + opcode: 'data_replaceitemoflist', + fields: [ + { + name: 'LIST', + list: listName + } + ], + inputs: [ + { + name: 'INDEX', + block: expectedInfo.makeNumber(1, 'math_integer') + }, + { + name: 'ITEM', + block: expectedInfo.makeText('thing') + } + ] + } + ]; + await convertAndExpectToEqualBlocks(converter, target, code, expected); + }); + + test('data_itemoflist with 1-indexed (no adjustment)', async () => { + const code = `list("${listName}")[1]`; + const expected = [ + { + opcode: 'data_itemoflist', + fields: [ + { + name: 'LIST', + list: listName + } + ], + inputs: [ + { + name: 'INDEX', + block: expectedInfo.makeNumber(1, 'math_integer') + } + ] + } + ]; + await convertAndExpectToEqualBlocks(converter, target, code, expected); + }); + + test('data_itemnumoflist via index (no subtract wrapper in v1)', async () => { + const code = `list("${listName}").index("thing")`; + const expected = [ + { + opcode: 'data_itemnumoflist', + fields: [ + { + name: 'LIST', + list: listName + } + ], + inputs: [ + { + name: 'ITEM', + block: expectedInfo.makeText('thing') + } + ] + } + ]; + await convertAndExpectToEqualBlocks(converter, target, code, expected); + }); + + test('data_lengthoflist via length', async () => { + const code = `list("${listName}").length`; + const expected = [ + { + opcode: 'data_lengthoflist', + fields: [ + { + name: 'LIST', + list: listName + } + ] + } + ]; + await convertAndExpectToEqualBlocks(converter, target, code, expected); + }); + + test('data_listcontainsitem via include?', async () => { + const code = `list("${listName}").include?("thing")`; + const expected = [ + { + opcode: 'data_listcontainsitem', + fields: [ + { + name: 'LIST', + list: listName + } + ], + inputs: [ + { + name: 'ITEM', + block: expectedInfo.makeText('thing') + } + ] + } + ]; + await convertAndExpectToEqualBlocks(converter, target, code, expected); + }); + }); + + describe('list("@a") - v1 instance list operations', () => { + const listName = '@a'; + + test('data_itemoflist with 1-indexed (no adjustment)', async () => { + const code = `list("${listName}")[1]`; + const expected = [ + { + opcode: 'data_itemoflist', + fields: [ + { + name: 'LIST', + list: listName + } + ], + inputs: [ + { + name: 'INDEX', + block: expectedInfo.makeNumber(1, 'math_integer') + } + ] + } + ]; + await convertAndExpectToEqualBlocks(converter, target, code, expected); + }); + + test('data_deleteoflist with 1-indexed (no adjustment)', async () => { + const code = `list("${listName}").delete_at(1)`; + const expected = [ + { + opcode: 'data_deleteoflist', + fields: [ + { + name: 'LIST', + list: listName + } + ], + inputs: [ + { + name: 'INDEX', + block: expectedInfo.makeNumber(1, 'math_integer') + } + ] + } + ]; + await convertAndExpectToEqualBlocks(converter, target, code, expected); + }); + }); + + describe('v1 index with higher values', () => { + const listName = '$a'; + + test('data_itemoflist with index 3 (stored as-is)', async () => { + const code = `list("${listName}")[3]`; + const expected = [ + { + opcode: 'data_itemoflist', + fields: [ + { + name: 'LIST', + list: listName + } + ], + inputs: [ + { + name: 'INDEX', + block: expectedInfo.makeNumber(3, 'math_integer') + } + ] + } + ]; + await convertAndExpectToEqualBlocks(converter, target, code, expected); + }); + + test('data_insertatlist with index 2 (stored as-is)', async () => { + const code = `list("${listName}").insert(2, "thing")`; + const expected = [ + { + opcode: 'data_insertatlist', + fields: [ + { + name: 'LIST', + list: listName + } + ], + inputs: [ + { + name: 'INDEX', + block: expectedInfo.makeNumber(2, 'math_integer') + }, + { + name: 'ITEM', + block: expectedInfo.makeText('thing') + } + ] + } + ]; + await convertAndExpectToEqualBlocks(converter, target, code, expected); + }); + }); +});