import defineDynamicBlock from '../../../src/lib/define-dynamic-block'; import BlockType from 'scratch-vm/src/extension-support/block-type'; const MockScratchBlocks = { OUTPUT_SHAPE_HEXAGONAL: 1, OUTPUT_SHAPE_ROUND: 2, OUTPUT_SHAPE_SQUARE: 3 }; const categoryInfo = { name: 'test category', color1: '#111', color2: '#222', color3: '#333' }; const penIconURI = 'data:image/svg+xml;base64,fake_pen_icon_svg_base64_data'; const testBlockInfo = { commandWithIcon: { blockType: BlockType.COMMAND, blockIconURI: penIconURI, text: 'command with icon' }, commandWithoutIcon: { blockType: BlockType.COMMAND, text: 'command without icon' }, terminalCommand: { blockType: BlockType.COMMAND, isTerminal: true, text: 'terminal command' }, reporter: { blockType: BlockType.REPORTER, text: 'reporter' }, boolean: { blockType: BlockType.BOOLEAN, text: 'Boolean' }, hat: { blockType: BlockType.HAT, text: 'hat' } }; // similar to goog.mixin from the Closure library const mixin = function (target, source) { for (const x in source) { target[x] = source[x]; } }; class MockBlock { constructor (blockInfo, extendedOpcode) { // mimic Closure-style inheritance by mixing in `defineDynamicBlock` output as this instance's prototype // see also the `Blockly.Block` constructor const prototype = defineDynamicBlock(MockScratchBlocks, categoryInfo, blockInfo, extendedOpcode); mixin(this, prototype); this.init(); // bootstrap the mutation<->DOM cycle this.blockInfoText = JSON.stringify(blockInfo); const xmlElement = this.mutationToDom(); // parse blockInfo from XML to fill dynamic properties this.domToMutation(xmlElement); } jsonInit (json) { this.result = Object.assign({}, json); } interpolate_ () { // TODO: add tests for this? } setCheckboxInFlyout (isEnabled) { this.result.checkboxInFlyout_ = isEnabled; } setOutput (isEnabled) { this.result.outputConnection = isEnabled; // Blockly calls `makeConnection_` here } setOutputShape (outputShape) { this.result.outputShape_ = outputShape; } setNextStatement (isEnabled) { this.result.nextConnection = isEnabled; // Blockly calls `makeConnection_` here } setPreviousStatement (isEnabled) { this.result.previousConnection = isEnabled; // Blockly calls `makeConnection_` here } } describe('defineDynamicBlock', () => { test('is a function', () => { expect(typeof defineDynamicBlock).toBe('function'); }); test('can define a command block with an icon', () => { const extendedOpcode = 'test.commandWithIcon'; const block = new MockBlock(testBlockInfo.commandWithIcon, extendedOpcode); expect(block.result).toEqual({ category: categoryInfo.name, colour: categoryInfo.color1, colourSecondary: categoryInfo.color2, colourTertiary: categoryInfo.color3, extensions: ['scratch_extension'], inputsInline: true, nextConnection: true, outputShape_: MockScratchBlocks.OUTPUT_SHAPE_SQUARE, previousConnection: true, type: extendedOpcode }); }); test('can define a command block without an icon', () => { const extendedOpcode = 'test.commandWithoutIcon'; const block = new MockBlock(testBlockInfo.commandWithoutIcon, extendedOpcode); expect(block.result).toEqual({ category: categoryInfo.name, colour: categoryInfo.color1, colourSecondary: categoryInfo.color2, colourTertiary: categoryInfo.color3, // extensions: undefined, // no icon means no extension inputsInline: true, nextConnection: true, outputShape_: MockScratchBlocks.OUTPUT_SHAPE_SQUARE, previousConnection: true, type: extendedOpcode }); }); test('can define a terminal command', () => { const extendedOpcode = 'test.terminal'; const block = new MockBlock(testBlockInfo.terminalCommand, extendedOpcode); expect(block.result).toEqual({ category: categoryInfo.name, colour: categoryInfo.color1, colourSecondary: categoryInfo.color2, colourTertiary: categoryInfo.color3, // extensions: undefined, // no icon means no extension inputsInline: true, nextConnection: false, // terminal outputShape_: MockScratchBlocks.OUTPUT_SHAPE_SQUARE, previousConnection: true, type: extendedOpcode }); }); test('can define a reporter', () => { const extendedOpcode = 'test.reporter'; const block = new MockBlock(testBlockInfo.reporter, extendedOpcode); expect(block.result).toEqual({ category: categoryInfo.name, checkboxInFlyout_: true, colour: categoryInfo.color1, colourSecondary: categoryInfo.color2, colourTertiary: categoryInfo.color3, // extensions: undefined, // no icon means no extension inputsInline: true, // nextConnection: undefined, // reporter outputConnection: true, // reporter outputShape_: MockScratchBlocks.OUTPUT_SHAPE_ROUND, // reporter // previousConnection: undefined, // reporter type: extendedOpcode }); }); test('can define a Boolean', () => { const extendedOpcode = 'test.boolean'; const block = new MockBlock(testBlockInfo.boolean, extendedOpcode); expect(block.result).toEqual({ category: categoryInfo.name, // checkboxInFlyout_: undefined, colour: categoryInfo.color1, colourSecondary: categoryInfo.color2, colourTertiary: categoryInfo.color3, // extensions: undefined, // no icon means no extension inputsInline: true, // nextConnection: undefined, // reporter outputConnection: true, // reporter outputShape_: MockScratchBlocks.OUTPUT_SHAPE_HEXAGONAL, // Boolean // previousConnection: undefined, // reporter type: extendedOpcode }); }); test('can define a hat', () => { const extendedOpcode = 'test.hat'; const block = new MockBlock(testBlockInfo.hat, extendedOpcode); expect(block.result).toEqual({ category: categoryInfo.name, colour: categoryInfo.color1, colourSecondary: categoryInfo.color2, colourTertiary: categoryInfo.color3, // extensions: undefined, // no icon means no extension inputsInline: true, nextConnection: true, outputShape_: MockScratchBlocks.OUTPUT_SHAPE_SQUARE, // previousConnection: undefined, // hat type: extendedOpcode }); }); });