define-dynamic-block.test.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. import defineDynamicBlock from '../../../src/lib/define-dynamic-block';
  2. import BlockType from 'scratch-vm/src/extension-support/block-type';
  3. const MockScratchBlocks = {
  4. OUTPUT_SHAPE_HEXAGONAL: 1,
  5. OUTPUT_SHAPE_ROUND: 2,
  6. OUTPUT_SHAPE_SQUARE: 3
  7. };
  8. const categoryInfo = {
  9. name: 'test category',
  10. color1: '#111',
  11. color2: '#222',
  12. color3: '#333'
  13. };
  14. const penIconURI = 'data:image/svg+xml;base64,fake_pen_icon_svg_base64_data';
  15. const testBlockInfo = {
  16. commandWithIcon: {
  17. blockType: BlockType.COMMAND,
  18. blockIconURI: penIconURI,
  19. text: 'command with icon'
  20. },
  21. commandWithoutIcon: {
  22. blockType: BlockType.COMMAND,
  23. text: 'command without icon'
  24. },
  25. terminalCommand: {
  26. blockType: BlockType.COMMAND,
  27. isTerminal: true,
  28. text: 'terminal command'
  29. },
  30. reporter: {
  31. blockType: BlockType.REPORTER,
  32. text: 'reporter'
  33. },
  34. boolean: {
  35. blockType: BlockType.BOOLEAN,
  36. text: 'Boolean'
  37. },
  38. hat: {
  39. blockType: BlockType.HAT,
  40. text: 'hat'
  41. }
  42. };
  43. // similar to goog.mixin from the Closure library
  44. const mixin = function (target, source) {
  45. for (const x in source) {
  46. target[x] = source[x];
  47. }
  48. };
  49. class MockBlock {
  50. constructor (blockInfo, extendedOpcode) {
  51. // mimic Closure-style inheritance by mixing in `defineDynamicBlock` output as this instance's prototype
  52. // see also the `Blockly.Block` constructor
  53. const prototype = defineDynamicBlock(MockScratchBlocks, categoryInfo, blockInfo, extendedOpcode);
  54. mixin(this, prototype);
  55. this.init();
  56. // bootstrap the mutation<->DOM cycle
  57. this.blockInfoText = JSON.stringify(blockInfo);
  58. const xmlElement = this.mutationToDom();
  59. // parse blockInfo from XML to fill dynamic properties
  60. this.domToMutation(xmlElement);
  61. }
  62. jsonInit (json) {
  63. this.result = Object.assign({}, json);
  64. }
  65. interpolate_ () {
  66. // TODO: add tests for this?
  67. }
  68. setCheckboxInFlyout (isEnabled) {
  69. this.result.checkboxInFlyout_ = isEnabled;
  70. }
  71. setOutput (isEnabled) {
  72. this.result.outputConnection = isEnabled; // Blockly calls `makeConnection_` here
  73. }
  74. setOutputShape (outputShape) {
  75. this.result.outputShape_ = outputShape;
  76. }
  77. setNextStatement (isEnabled) {
  78. this.result.nextConnection = isEnabled; // Blockly calls `makeConnection_` here
  79. }
  80. setPreviousStatement (isEnabled) {
  81. this.result.previousConnection = isEnabled; // Blockly calls `makeConnection_` here
  82. }
  83. }
  84. describe('defineDynamicBlock', () => {
  85. test('is a function', () => {
  86. expect(typeof defineDynamicBlock).toBe('function');
  87. });
  88. test('can define a command block with an icon', () => {
  89. const extendedOpcode = 'test.commandWithIcon';
  90. const block = new MockBlock(testBlockInfo.commandWithIcon, extendedOpcode);
  91. expect(block.result).toEqual({
  92. category: categoryInfo.name,
  93. colour: categoryInfo.color1,
  94. colourSecondary: categoryInfo.color2,
  95. colourTertiary: categoryInfo.color3,
  96. extensions: ['scratch_extension'],
  97. inputsInline: true,
  98. nextConnection: true,
  99. outputShape_: MockScratchBlocks.OUTPUT_SHAPE_SQUARE,
  100. previousConnection: true,
  101. type: extendedOpcode
  102. });
  103. });
  104. test('can define a command block without an icon', () => {
  105. const extendedOpcode = 'test.commandWithoutIcon';
  106. const block = new MockBlock(testBlockInfo.commandWithoutIcon, extendedOpcode);
  107. expect(block.result).toEqual({
  108. category: categoryInfo.name,
  109. colour: categoryInfo.color1,
  110. colourSecondary: categoryInfo.color2,
  111. colourTertiary: categoryInfo.color3,
  112. // extensions: undefined, // no icon means no extension
  113. inputsInline: true,
  114. nextConnection: true,
  115. outputShape_: MockScratchBlocks.OUTPUT_SHAPE_SQUARE,
  116. previousConnection: true,
  117. type: extendedOpcode
  118. });
  119. });
  120. test('can define a terminal command', () => {
  121. const extendedOpcode = 'test.terminal';
  122. const block = new MockBlock(testBlockInfo.terminalCommand, extendedOpcode);
  123. expect(block.result).toEqual({
  124. category: categoryInfo.name,
  125. colour: categoryInfo.color1,
  126. colourSecondary: categoryInfo.color2,
  127. colourTertiary: categoryInfo.color3,
  128. // extensions: undefined, // no icon means no extension
  129. inputsInline: true,
  130. nextConnection: false, // terminal
  131. outputShape_: MockScratchBlocks.OUTPUT_SHAPE_SQUARE,
  132. previousConnection: true,
  133. type: extendedOpcode
  134. });
  135. });
  136. test('can define a reporter', () => {
  137. const extendedOpcode = 'test.reporter';
  138. const block = new MockBlock(testBlockInfo.reporter, extendedOpcode);
  139. expect(block.result).toEqual({
  140. category: categoryInfo.name,
  141. checkboxInFlyout_: true,
  142. colour: categoryInfo.color1,
  143. colourSecondary: categoryInfo.color2,
  144. colourTertiary: categoryInfo.color3,
  145. // extensions: undefined, // no icon means no extension
  146. inputsInline: true,
  147. // nextConnection: undefined, // reporter
  148. outputConnection: true, // reporter
  149. outputShape_: MockScratchBlocks.OUTPUT_SHAPE_ROUND, // reporter
  150. // previousConnection: undefined, // reporter
  151. type: extendedOpcode
  152. });
  153. });
  154. test('can define a Boolean', () => {
  155. const extendedOpcode = 'test.boolean';
  156. const block = new MockBlock(testBlockInfo.boolean, extendedOpcode);
  157. expect(block.result).toEqual({
  158. category: categoryInfo.name,
  159. // checkboxInFlyout_: undefined,
  160. colour: categoryInfo.color1,
  161. colourSecondary: categoryInfo.color2,
  162. colourTertiary: categoryInfo.color3,
  163. // extensions: undefined, // no icon means no extension
  164. inputsInline: true,
  165. // nextConnection: undefined, // reporter
  166. outputConnection: true, // reporter
  167. outputShape_: MockScratchBlocks.OUTPUT_SHAPE_HEXAGONAL, // Boolean
  168. // previousConnection: undefined, // reporter
  169. type: extendedOpcode
  170. });
  171. });
  172. test('can define a hat', () => {
  173. const extendedOpcode = 'test.hat';
  174. const block = new MockBlock(testBlockInfo.hat, extendedOpcode);
  175. expect(block.result).toEqual({
  176. category: categoryInfo.name,
  177. colour: categoryInfo.color1,
  178. colourSecondary: categoryInfo.color2,
  179. colourTertiary: categoryInfo.color3,
  180. // extensions: undefined, // no icon means no extension
  181. inputsInline: true,
  182. nextConnection: true,
  183. outputShape_: MockScratchBlocks.OUTPUT_SHAPE_SQUARE,
  184. // previousConnection: undefined, // hat
  185. type: extendedOpcode
  186. });
  187. });
  188. });