diff --git a/.prettierignore b/.prettierignore index 8ab97fd3c9..8415c4d8e8 100644 --- a/.prettierignore +++ b/.prettierignore @@ -21,3 +21,5 @@ PULL_REQUEST_TEMPLATE.md plugins/dev-create/templates/sample-app/* plugins/dev-create/templates/sample-app-ts/* +# Golden test files +plugins/**/golden/* diff --git a/eslint.config.js b/eslint.config.js index 2c4ab1feb0..1e6de9b318 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -44,6 +44,8 @@ module.exports = [ // specific examples that are sometimes copied into plugins 'plugins/dev-create/templates/sample-app', 'plugins/dev-create/templates/sample-app-ts', + // Golden test files + 'plugins/**/golden/*', ], }, js.configs.recommended, // eslint-recommended diff --git a/plugins/field-colour/package-lock.json b/plugins/field-colour/package-lock.json index e4e36dd4e8..cf050c142b 100644 --- a/plugins/field-colour/package-lock.json +++ b/plugins/field-colour/package-lock.json @@ -10,7 +10,7 @@ "license": "Apache-2.0", "devDependencies": { "@blockly/dev-scripts": "^3.1.1", - "@blockly/dev-tools": "^7.1.5", + "@blockly/dev-tools": "^7.1.6", "@typescript-eslint/parser": "^5.59.5", "blockly": "^11.0.0-beta.3", "chai": "^4.2.0", @@ -378,12 +378,12 @@ } }, "node_modules/@blockly/dev-tools": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/@blockly/dev-tools/-/dev-tools-7.1.5.tgz", - "integrity": "sha512-eNpi+yknoR2RXzYUOE4Owp5XiY9Qm/FH+BOGhd5WrDyK3/LGQ4Yp0NOBO1tWN+Kbjxe19k6yhA50kX7YkDad/w==", + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/@blockly/dev-tools/-/dev-tools-7.1.6.tgz", + "integrity": "sha512-RvjwoM37tIDbdQeUjGecfTQtEM/NCtZh2iKQue1mtqzJZ6uHXm6TQsTTq849/vsCEQs6LMgpUN6rpjp/yj1Pzg==", "dev": true, "dependencies": { - "@blockly/block-test": "^5.0.4", + "@blockly/block-test": "^5.1.0", "@blockly/theme-dark": "^6.0.5", "@blockly/theme-deuteranopia": "^5.0.5", "@blockly/theme-highcontrast": "^5.0.5", @@ -403,9 +403,9 @@ } }, "node_modules/@blockly/dev-tools/node_modules/@blockly/block-test": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@blockly/block-test/-/block-test-5.0.4.tgz", - "integrity": "sha512-3u7z9Xd+W1eCcknqVsmUYnDvS2FIzce2IdGuRC1lst2XIQQ59q2wkUqGg4+Z2Arr3hk/TpIlHn2YLrGPkvqkug==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@blockly/block-test/-/block-test-5.1.0.tgz", + "integrity": "sha512-beqBTJbrrDGECohJo6uczeLAvRxKgMFn9Ew1po6d8PBka/aNwSkT33jHRUAeasHnraBdFBx8pwYh7By2gVZURQ==", "dev": true, "engines": { "node": ">=8.17.0" @@ -7163,12 +7163,12 @@ } }, "@blockly/dev-tools": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/@blockly/dev-tools/-/dev-tools-7.1.5.tgz", - "integrity": "sha512-eNpi+yknoR2RXzYUOE4Owp5XiY9Qm/FH+BOGhd5WrDyK3/LGQ4Yp0NOBO1tWN+Kbjxe19k6yhA50kX7YkDad/w==", + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/@blockly/dev-tools/-/dev-tools-7.1.6.tgz", + "integrity": "sha512-RvjwoM37tIDbdQeUjGecfTQtEM/NCtZh2iKQue1mtqzJZ6uHXm6TQsTTq849/vsCEQs6LMgpUN6rpjp/yj1Pzg==", "dev": true, "requires": { - "@blockly/block-test": "^5.0.4", + "@blockly/block-test": "^5.1.0", "@blockly/theme-dark": "^6.0.5", "@blockly/theme-deuteranopia": "^5.0.5", "@blockly/theme-highcontrast": "^5.0.5", @@ -7182,9 +7182,9 @@ }, "dependencies": { "@blockly/block-test": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@blockly/block-test/-/block-test-5.0.4.tgz", - "integrity": "sha512-3u7z9Xd+W1eCcknqVsmUYnDvS2FIzce2IdGuRC1lst2XIQQ59q2wkUqGg4+Z2Arr3hk/TpIlHn2YLrGPkvqkug==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@blockly/block-test/-/block-test-5.1.0.tgz", + "integrity": "sha512-beqBTJbrrDGECohJo6uczeLAvRxKgMFn9Ew1po6d8PBka/aNwSkT33jHRUAeasHnraBdFBx8pwYh7By2gVZURQ==", "dev": true, "requires": {} }, diff --git a/plugins/field-colour/src/blocks/colourBlend.ts b/plugins/field-colour/src/blocks/colourBlend.ts index 78919312e9..3aaed82d7d 100644 --- a/plugins/field-colour/src/blocks/colourBlend.ts +++ b/plugins/field-colour/src/blocks/colourBlend.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {Block, common as BlocklyCommon} from 'blockly'; +import {Block, common as BlocklyCommon} from 'blockly/core'; import { JavascriptGenerator, Order as JavascriptOrder, diff --git a/plugins/field-colour/src/blocks/colourPicker.ts b/plugins/field-colour/src/blocks/colourPicker.ts index 4f1dbc3a42..23c6fcc95e 100644 --- a/plugins/field-colour/src/blocks/colourPicker.ts +++ b/plugins/field-colour/src/blocks/colourPicker.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {Block, common as BlocklyCommon} from 'blockly'; +import {Block, common as BlocklyCommon} from 'blockly/core'; import { JavascriptGenerator, Order as JavascriptOrder, diff --git a/plugins/field-colour/src/blocks/colourRandom.ts b/plugins/field-colour/src/blocks/colourRandom.ts index 9f4c226ee7..038ffa5aa1 100644 --- a/plugins/field-colour/src/blocks/colourRandom.ts +++ b/plugins/field-colour/src/blocks/colourRandom.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {Block, common as BlocklyCommon} from 'blockly'; +import {Block, common as BlocklyCommon} from 'blockly/core'; import { JavascriptGenerator, Order as JavascriptOrder, diff --git a/plugins/field-colour/src/blocks/colourRgb.ts b/plugins/field-colour/src/blocks/colourRgb.ts index 8292f87a1e..de602e94ce 100644 --- a/plugins/field-colour/src/blocks/colourRgb.ts +++ b/plugins/field-colour/src/blocks/colourRgb.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {Block, common as BlocklyCommon} from 'blockly'; +import {Block, common as BlocklyCommon} from 'blockly/core'; import { JavascriptGenerator, Order as JavascriptOrder, diff --git a/plugins/field-colour/test/blocks_test.mocha.js b/plugins/field-colour/test/blocks_test.mocha.js new file mode 100644 index 0000000000..3d0f4f4999 --- /dev/null +++ b/plugins/field-colour/test/blocks_test.mocha.js @@ -0,0 +1,254 @@ +/** + * @license + * Copyright 2024 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import fs from 'fs'; +import * as Blockly from 'blockly/core'; +import 'blockly/blocks'; + +import {javascriptGenerator} from 'blockly/javascript'; +import {dartGenerator} from 'blockly/dart'; +import {phpGenerator} from 'blockly/php'; +import {pythonGenerator} from 'blockly/python'; +import {luaGenerator} from 'blockly/lua'; +import {installAllBlocks} from '../src/index'; +import {assert} from 'chai'; + +const blockJson = { + blocks: { + languageVersion: 0, + blocks: [ + { + type: 'colour_picker', + x: 13, + y: 13, + fields: { + COLOUR: '#ff0000', + }, + }, + { + type: 'colour_random', + x: 13, + y: 113, + }, + { + type: 'colour_rgb', + x: 13, + y: 263, + inputs: { + RED: { + shadow: { + type: 'math_number', + fields: { + NUM: 0, + }, + }, + }, + GREEN: { + shadow: { + type: 'math_number', + fields: { + NUM: 1, + }, + }, + }, + BLUE: { + shadow: { + type: 'math_number', + fields: { + NUM: 20, + }, + }, + }, + }, + }, + { + type: 'colour_blend', + x: 13, + y: 363, + inputs: { + COLOUR1: { + shadow: { + type: 'colour_picker', + fields: { + COLOUR: '#ff0000', + }, + }, + }, + COLOUR2: { + shadow: { + type: 'colour_picker', + fields: { + COLOUR: '#3333ff', + }, + }, + }, + RATIO: { + shadow: { + type: 'math_number', + fields: { + NUM: 0.5, + }, + }, + }, + }, + }, + { + type: 'colour_picker', + x: 13, + y: 63, + fields: { + COLOUR: '#3333ff', + }, + }, + { + type: 'colour_rgb', + x: 13, + y: 163, + inputs: { + RED: { + shadow: { + type: 'math_number', + fields: { + NUM: 100, + }, + }, + }, + GREEN: { + shadow: { + type: 'math_number', + fields: { + NUM: 50, + }, + }, + }, + BLUE: { + shadow: { + type: 'math_number', + fields: { + NUM: 0, + }, + }, + }, + }, + }, + { + type: 'colour_blend', + x: 13, + y: 463, + inputs: { + COLOUR1: { + shadow: { + type: 'colour_picker', + fields: { + COLOUR: '#000000', + }, + }, + }, + COLOUR2: { + shadow: { + type: 'colour_picker', + fields: { + COLOUR: '#ffffff', + }, + }, + }, + RATIO: { + shadow: { + type: 'math_number', + fields: { + NUM: 0.3, + }, + }, + }, + }, + }, + ], + }, +}; + +/** + * Uninstall old blocks and generators, since they come pre-installed + * with Blockly. + * TODO(#2194): Delete this function and calls to it. + */ +function uninstallBlocks() { + delete Blockly.Blocks['colour_blend']; + delete Blockly.Blocks['colour_rgb']; + delete Blockly.Blocks['colour_random']; + delete Blockly.Blocks['colour_picker']; + + const blockNames = [ + 'colour_blend', + 'colour_rgb', + 'colour_random', + 'colour_picker', + ]; + blockNames.forEach((name) => { + delete javascriptGenerator.forBlock[name]; + delete dartGenerator.forBlock[name]; + delete luaGenerator.forBlock[name]; + delete pythonGenerator.forBlock[name]; + delete phpGenerator.forBlock[name]; + }); +} + +/** + * Assert that the generated code matches the golden code for the specified + * language. + * @param {string} suffix The suffix of the golden file. + * @param {string} generated The generated code to compare against the + * golden file. + */ +function checkResult(suffix, generated) { + const fileName = `test/golden/golden.${suffix}`; + const goldenContents = fs.readFileSync(fileName); + // Normalize the line feeds. + const normalized = goldenContents.toString().replace(/(?:\r\n|\r|\n)/g, '\n'); + assert.equal(generated, normalized); +} + +suite('Colour Block Generators', function () { + suiteSetup(function () { + uninstallBlocks(); + installAllBlocks({ + javascript: javascriptGenerator, + dart: dartGenerator, + lua: luaGenerator, + python: pythonGenerator, + php: phpGenerator, + }); + }); + setup(function () { + this.workspace = new Blockly.Workspace(); + Blockly.serialization.workspaces.load(blockJson, this.workspace); + }); + test('JavaScript', function () { + const generated = javascriptGenerator.workspaceToCode(this.workspace); + checkResult('js', generated); + }); + test('Dart', function () { + const generated = dartGenerator.workspaceToCode(this.workspace); + checkResult('dart', generated); + }); + test('Lua', function () { + const generated = luaGenerator.workspaceToCode(this.workspace); + checkResult('lua', generated); + }); + test('Python', function () { + const generated = pythonGenerator.workspaceToCode(this.workspace); + checkResult('py', generated); + }); + test('PHP', function () { + const generated = phpGenerator.workspaceToCode(this.workspace); + checkResult('php', generated); + }); + teardown(function () { + this.workspace.dispose(); + }); + suiteTeardown(function () { + uninstallBlocks(); + }); +}); diff --git a/plugins/field-colour/test/golden/golden.dart b/plugins/field-colour/test/golden/golden.dart new file mode 100644 index 0000000000..80f9f81930 --- /dev/null +++ b/plugins/field-colour/test/golden/golden.dart @@ -0,0 +1,65 @@ +import 'dart:math' as Math; + +String colour_random() { + String hex = '0123456789abcdef'; + var rnd = new Math.Random(); + return '#${hex[rnd.nextInt(16)]}${hex[rnd.nextInt(16)]}' + '${hex[rnd.nextInt(16)]}${hex[rnd.nextInt(16)]}' + '${hex[rnd.nextInt(16)]}${hex[rnd.nextInt(16)]}'; +} + +String colour_rgb(num r, num g, num b) { + num rn = (Math.max(Math.min(r, 100), 0) * 2.55).round(); + String rs = rn.toInt().toRadixString(16); + rs = '0$rs'; + rs = rs.substring(rs.length - 2); + num gn = (Math.max(Math.min(g, 100), 0) * 2.55).round(); + String gs = gn.toInt().toRadixString(16); + gs = '0$gs'; + gs = gs.substring(gs.length - 2); + num bn = (Math.max(Math.min(b, 100), 0) * 2.55).round(); + String bs = bn.toInt().toRadixString(16); + bs = '0$bs'; + bs = bs.substring(bs.length - 2); + return '#$rs$gs$bs'; +} + +String colour_blend(String c1, String c2, num ratio) { + ratio = Math.max(Math.min(ratio, 1), 0); + int r1 = int.parse('0x${c1.substring(1, 3)}'); + int g1 = int.parse('0x${c1.substring(3, 5)}'); + int b1 = int.parse('0x${c1.substring(5, 7)}'); + int r2 = int.parse('0x${c2.substring(1, 3)}'); + int g2 = int.parse('0x${c2.substring(3, 5)}'); + int b2 = int.parse('0x${c2.substring(5, 7)}'); + num rn = (r1 * (1 - ratio) + r2 * ratio).round(); + String rs = rn.toInt().toRadixString(16); + num gn = (g1 * (1 - ratio) + g2 * ratio).round(); + String gs = gn.toInt().toRadixString(16); + num bn = (b1 * (1 - ratio) + b2 * ratio).round(); + String bs = bn.toInt().toRadixString(16); + rs = '0$rs'; + rs = rs.substring(rs.length - 2); + gs = '0$gs'; + gs = gs.substring(gs.length - 2); + bs = '0$bs'; + bs = bs.substring(bs.length - 2); + return '#$rs$gs$bs'; +} + + +main() { + '#ff0000'; + + '#3333ff'; + + colour_random(); + + colour_rgb(100, 50, 0); + + colour_rgb(0, 1, 20); + + colour_blend('#ff0000', '#3333ff', 0.5); + + colour_blend('#000000', '#ffffff', 0.3); +} \ No newline at end of file diff --git a/plugins/field-colour/test/golden/golden.js b/plugins/field-colour/test/golden/golden.js new file mode 100644 index 0000000000..c9354913e7 --- /dev/null +++ b/plugins/field-colour/test/golden/golden.js @@ -0,0 +1,46 @@ +function colourRandom() { + var num = Math.floor(Math.random() * Math.pow(2, 24)); + return '#' + ('00000' + num.toString(16)).substr(-6); +} + +function colourRgb(r, g, b) { + r = Math.max(Math.min(Number(r), 100), 0) * 2.55; + g = Math.max(Math.min(Number(g), 100), 0) * 2.55; + b = Math.max(Math.min(Number(b), 100), 0) * 2.55; + r = ('0' + (Math.round(r) || 0).toString(16)).slice(-2); + g = ('0' + (Math.round(g) || 0).toString(16)).slice(-2); + b = ('0' + (Math.round(b) || 0).toString(16)).slice(-2); + return '#' + r + g + b; +} + +function colourBlend(c1, c2, ratio) { + ratio = Math.max(Math.min(Number(ratio), 1), 0); + var r1 = parseInt(c1.substring(1, 3), 16); + var g1 = parseInt(c1.substring(3, 5), 16); + var b1 = parseInt(c1.substring(5, 7), 16); + var r2 = parseInt(c2.substring(1, 3), 16); + var g2 = parseInt(c2.substring(3, 5), 16); + var b2 = parseInt(c2.substring(5, 7), 16); + var r = Math.round(r1 * (1 - ratio) + r2 * ratio); + var g = Math.round(g1 * (1 - ratio) + g2 * ratio); + var b = Math.round(b1 * (1 - ratio) + b2 * ratio); + r = ('0' + (r || 0).toString(16)).slice(-2); + g = ('0' + (g || 0).toString(16)).slice(-2); + b = ('0' + (b || 0).toString(16)).slice(-2); + return '#' + r + g + b; +} + + +'#ff0000'; + +'#3333ff'; + +colourRandom(); + +colourRgb(100, 50, 0); + +colourRgb(0, 1, 20); + +colourBlend('#ff0000', '#3333ff', 0.5); + +colourBlend('#000000', '#ffffff', 0.3); diff --git a/plugins/field-colour/test/golden/golden.lua b/plugins/field-colour/test/golden/golden.lua new file mode 100644 index 0000000000..4fdf506d46 --- /dev/null +++ b/plugins/field-colour/test/golden/golden.lua @@ -0,0 +1,35 @@ +function colour_rgb(r, g, b) + r = math.floor(math.min(100, math.max(0, r)) * 2.55 + .5) + g = math.floor(math.min(100, math.max(0, g)) * 2.55 + .5) + b = math.floor(math.min(100, math.max(0, b)) * 2.55 + .5) + return string.format("#%02x%02x%02x", r, g, b) +end + +function colour_blend(colour1, colour2, ratio) + local r1 = tonumber(string.sub(colour1, 2, 3), 16) + local r2 = tonumber(string.sub(colour2, 2, 3), 16) + local g1 = tonumber(string.sub(colour1, 4, 5), 16) + local g2 = tonumber(string.sub(colour2, 4, 5), 16) + local b1 = tonumber(string.sub(colour1, 6, 7), 16) + local b2 = tonumber(string.sub(colour2, 6, 7), 16) + local ratio = math.min(1, math.max(0, ratio)) + local r = math.floor(r1 * (1 - ratio) + r2 * ratio + .5) + local g = math.floor(g1 * (1 - ratio) + g2 * ratio + .5) + local b = math.floor(b1 * (1 - ratio) + b2 * ratio + .5) + return string.format("#%02x%02x%02x", r, g, b) +end + + +local _ = '#ff0000' + +local _ = '#3333ff' + +local _ = string.format("#%06x", math.random(0, 2^24 - 1)) + +local _ = colour_rgb(100, 50, 0) + +local _ = colour_rgb(0, 1, 20) + +local _ = colour_blend('#ff0000', '#3333ff', 0.5) + +local _ = colour_blend('#000000', '#ffffff', 0.3) diff --git a/plugins/field-colour/test/golden/golden.php b/plugins/field-colour/test/golden/golden.php new file mode 100644 index 0000000000..9f2fde50aa --- /dev/null +++ b/plugins/field-colour/test/golden/golden.php @@ -0,0 +1,47 @@ +function colour_random() { + return '#' . str_pad(dechex(mt_rand(0, 0xFFFFFF)), 6, '0', STR_PAD_LEFT); +} + +function colour_rgb($r, $g, $b) { + $r = round(max(min($r, 100), 0) * 2.55); + $g = round(max(min($g, 100), 0) * 2.55); + $b = round(max(min($b, 100), 0) * 2.55); + $hex = '#'; + $hex .= str_pad(dechex($r), 2, '0', STR_PAD_LEFT); + $hex .= str_pad(dechex($g), 2, '0', STR_PAD_LEFT); + $hex .= str_pad(dechex($b), 2, '0', STR_PAD_LEFT); + return $hex; +} + +function colour_blend($c1, $c2, $ratio) { + $ratio = max(min($ratio, 1), 0); + $r1 = hexdec(substr($c1, 1, 2)); + $g1 = hexdec(substr($c1, 3, 2)); + $b1 = hexdec(substr($c1, 5, 2)); + $r2 = hexdec(substr($c2, 1, 2)); + $g2 = hexdec(substr($c2, 3, 2)); + $b2 = hexdec(substr($c2, 5, 2)); + $r = round($r1 * (1 - $ratio) + $r2 * $ratio); + $g = round($g1 * (1 - $ratio) + $g2 * $ratio); + $b = round($b1 * (1 - $ratio) + $b2 * $ratio); + $hex = '#'; + $hex .= str_pad(dechex($r), 2, '0', STR_PAD_LEFT); + $hex .= str_pad(dechex($g), 2, '0', STR_PAD_LEFT); + $hex .= str_pad(dechex($b), 2, '0', STR_PAD_LEFT); + return $hex; +} + + +'#ff0000'; + +'#3333ff'; + +colour_random(); + +colour_rgb(100, 50, 0); + +colour_rgb(0, 1, 20); + +colour_blend('#ff0000', '#3333ff', 0.5); + +colour_blend('#000000', '#ffffff', 0.3); diff --git a/plugins/field-colour/test/golden/golden.py b/plugins/field-colour/test/golden/golden.py new file mode 100644 index 0000000000..7320b86247 --- /dev/null +++ b/plugins/field-colour/test/golden/golden.py @@ -0,0 +1,32 @@ +import random + +def colour_rgb(r, g, b): + r = round(min(100, max(0, r)) * 2.55) + g = round(min(100, max(0, g)) * 2.55) + b = round(min(100, max(0, b)) * 2.55) + return '#%02x%02x%02x' % (r, g, b) + +def colour_blend(colour1, colour2, ratio): + r1, r2 = int(colour1[1:3], 16), int(colour2[1:3], 16) + g1, g2 = int(colour1[3:5], 16), int(colour2[3:5], 16) + b1, b2 = int(colour1[5:7], 16), int(colour2[5:7], 16) + ratio = min(1, max(0, ratio)) + r = round(r1 * (1 - ratio) + r2 * ratio) + g = round(g1 * (1 - ratio) + g2 * ratio) + b = round(b1 * (1 - ratio) + b2 * ratio) + return '#%02x%02x%02x' % (r, g, b) + + +'#ff0000' + +'#3333ff' + +'#%06x' % random.randint(0, 2**24 - 1) + +colour_rgb(100, 50, 0) + +colour_rgb(0, 1, 20) + +colour_blend('#ff0000', '#3333ff', 0.5) + +colour_blend('#000000', '#ffffff', 0.3)