Initial commit of 001code-html Scratch frontend project.
Includes scratch-gui, scratch-vm, scratch-blocks, scratch-render, scratch-l10n, and deployment config. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
199
scratch-gui/test/unit/util/define-dynamic-block.test.js
Normal file
199
scratch-gui/test/unit/util/define-dynamic-block.test.js
Normal file
@@ -0,0 +1,199 @@
|
||||
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
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user