From 4dcf4064f2e1189420583f5d6e1a116715bd32d7 Mon Sep 17 00:00:00 2001 From: Christopher Allen Date: Tue, 19 Dec 2023 21:20:04 +0000 Subject: [PATCH 01/92] fix(generators): Fix generator typings (#7727) --- scripts/gulpfiles/package_tasks.js | 1 - typings/dart.d.ts | 23 +--------------- typings/javascript.d.ts | 40 +--------------------------- typings/lua.d.ts | 17 +----------- typings/php.d.ts | 42 +----------------------------- typings/python.d.ts | 26 +----------------- 6 files changed, 5 insertions(+), 144 deletions(-) diff --git a/scripts/gulpfiles/package_tasks.js b/scripts/gulpfiles/package_tasks.js index 38a5b5f9a..46d91ec5f 100644 --- a/scripts/gulpfiles/package_tasks.js +++ b/scripts/gulpfiles/package_tasks.js @@ -348,7 +348,6 @@ function packageDTS() { return gulp.src(handwrittenSrcs, {base: 'typings'}) .pipe(gulp.src(`${TYPINGS_BUILD_DIR}/**/*.d.ts`, {ignore: [ `${TYPINGS_BUILD_DIR}/blocks/**/*`, - `${TYPINGS_BUILD_DIR}/generators/**/*`, ]})) .pipe(gulp.replace('AnyDuringMigration', 'any')) .pipe(gulp.dest(RELEASE_DIR)); diff --git a/typings/dart.d.ts b/typings/dart.d.ts index 4dda479a4..c2e6ccc90 100644 --- a/typings/dart.d.ts +++ b/typings/dart.d.ts @@ -4,25 +4,4 @@ * SPDX-License-Identifier: Apache-2.0 */ -export enum Order { - ATOMIC = 0, // 0 "" ... - UNARY_POSTFIX = 1, // expr++ expr-- () [] . ?. - UNARY_PREFIX = 2, // -expr !expr ~expr ++expr --expr - MULTIPLICATIVE = 3, // * / % ~/ - ADDITIVE = 4, // + - - SHIFT = 5, // << >> - BITWISE_AND = 6, // & - BITWISE_XOR = 7, // ^ - BITWISE_OR = 8, // | - RELATIONAL = 9, // >= > <= < as is is! - EQUALITY = 10, // == != - LOGICAL_AND = 11, // && - LOGICAL_OR = 12, // || - IF_NULL = 13, // ?? - CONDITIONAL = 14, // expr ? expr : expr - CASCADE = 15, // .. - ASSIGNMENT = 16, // = *= /= ~/= %= += -= <<= >>= &= ^= |= - NONE = 99, // (...) -} - -export declare const dartGenerator: any; +export * from 'generators/dart'; diff --git a/typings/javascript.d.ts b/typings/javascript.d.ts index b507a9be4..e71275f64 100644 --- a/typings/javascript.d.ts +++ b/typings/javascript.d.ts @@ -4,42 +4,4 @@ * SPDX-License-Identifier: Apache-2.0 */ -export enum Order { - ATOMIC = 0, // 0 "" ... - NEW = 1.1, // new - MEMBER = 1.2, // . [] - FUNCTION_CALL = 2, // () - INCREMENT = 3, // ++ - DECREMENT = 3, // -- - BITWISE_NOT = 4.1, // ~ - UNARY_PLUS = 4.2, // + - UNARY_NEGATION = 4.3, // - - LOGICAL_NOT = 4.4, // ! - TYPEOF = 4.5, // typeof - VOID = 4.6, // void - DELETE = 4.7, // delete - AWAIT = 4.8, // await - EXPONENTIATION = 5.0, // ** - MULTIPLICATION = 5.1, // * - DIVISION = 5.2, // / - MODULUS = 5.3, // % - SUBTRACTION = 6.1, // - - ADDITION = 6.2, // + - BITWISE_SHIFT = 7, // << >> >>> - RELATIONAL = 8, // < <= > >= - IN = 8, // in - INSTANCEOF = 8, // instanceof - EQUALITY = 9, // == != === !== - BITWISE_AND = 10, // & - BITWISE_XOR = 11, // ^ - BITWISE_OR = 12, // | - LOGICAL_AND = 13, // && - LOGICAL_OR = 14, // || - CONDITIONAL = 15, // ?: - ASSIGNMENT = 16, // = += -= **= *= /= %= <<= >>= ... - YIELD = 17, // yield - COMMA = 18, // , - NONE = 99, // (...) -} - -export declare const javascriptGenerator: any; +export * from 'generators/javascript'; diff --git a/typings/lua.d.ts b/typings/lua.d.ts index 693fb0ba2..444b22fc1 100644 --- a/typings/lua.d.ts +++ b/typings/lua.d.ts @@ -4,19 +4,4 @@ * SPDX-License-Identifier: Apache-2.0 */ -export enum Order { - ATOMIC = 0, // literals - // The next level was not explicit in documentation and inferred by Ellen. - HIGH = 1, // Function calls, tables[] - EXPONENTIATION = 2, // ^ - UNARY = 3, // not # - ~ - MULTIPLICATIVE = 4, // * / % - ADDITIVE = 5, // + - - CONCATENATION = 6, // .. - RELATIONAL = 7, // < > <= >= ~= == - AND = 8, // and - OR = 9, // or - NONE = 99, -} - -export declare const luaGenerator: any; +export * from 'generators/lua'; diff --git a/typings/php.d.ts b/typings/php.d.ts index 29a223613..6e907926c 100644 --- a/typings/php.d.ts +++ b/typings/php.d.ts @@ -4,44 +4,4 @@ * SPDX-License-Identifier: Apache-2.0 */ -export enum Order { - ATOMIC = 0, // 0 "" ... - CLONE = 1, // clone - NEW = 1, // new - MEMBER = 2.1, // [] - FUNCTION_CALL = 2.2, // () - POWER = 3, // ** - INCREMENT = 4, // ++ - DECREMENT = 4, // -- - BITWISE_NOT = 4, // ~ - CAST = 4, // (int) (float) (string) (array) ... - SUPPRESS_ERROR = 4, // @ - INSTANCEOF = 5, // instanceof - LOGICAL_NOT = 6, // ! - UNARY_PLUS = 7.1, // + - UNARY_NEGATION = 7.2, // - - MULTIPLICATION = 8.1, // * - DIVISION = 8.2, // / - MODULUS = 8.3, // % - ADDITION = 9.1, // + - SUBTRACTION = 9.2, // - - STRING_CONCAT = 9.3, // . - BITWISE_SHIFT = 10, // << >> - RELATIONAL = 11, // < <= > >= - EQUALITY = 12, // == != === !== <> <=> - REFERENCE = 13, // & - BITWISE_AND = 13, // & - BITWISE_XOR = 14, // ^ - BITWISE_OR = 15, // | - LOGICAL_AND = 16, // && - LOGICAL_OR = 17, // || - IF_NULL = 18, // ?? - CONDITIONAL = 19, // ?: - ASSIGNMENT = 20, // = += -= *= /= %= <<= >>= ... - LOGICAL_AND_WEAK = 21, // and - LOGICAL_XOR = 22, // xor - LOGICAL_OR_WEAK = 23, // or - NONE = 99, // (...) -} - -export declare const phpGenerator: any; +export * from 'generators/php'; diff --git a/typings/python.d.ts b/typings/python.d.ts index 6d6449e37..ff6cc4f9f 100644 --- a/typings/python.d.ts +++ b/typings/python.d.ts @@ -4,28 +4,4 @@ * SPDX-License-Identifier: Apache-2.0 */ -export enum Order { - ATOMIC = 0, // 0 "" ... - COLLECTION = 1, // tuples, lists, dictionaries - STRING_CONVERSION = 1, // `expression...` - MEMBER = 2.1, // . [] - FUNCTION_CALL = 2.2, // () - EXPONENTIATION = 3, // ** - UNARY_SIGN = 4, // + - - BITWISE_NOT = 4, // ~ - MULTIPLICATIVE = 5, // * / // % - ADDITIVE = 6, // + - - BITWISE_SHIFT = 7, // << >> - BITWISE_AND = 8, // & - BITWISE_XOR = 9, // ^ - BITWISE_OR = 10, // | - RELATIONAL = 11, // in, not in, is, is not, >, >=, <>, !=, == - LOGICAL_NOT = 12, // not - LOGICAL_AND = 13, // and - LOGICAL_OR = 14, // or - CONDITIONAL = 15, // if else - LAMBDA = 16, // lambda - NONE = 99, // (...) -} - -export declare const pythonGenerator: any; +export * from 'generators/python'; From 2a20a3a30399972ac0be64915db1a6210f5f32e6 Mon Sep 17 00:00:00 2001 From: Christopher Allen Date: Sat, 23 Dec 2023 22:48:23 +0000 Subject: [PATCH 02/92] release: Update version number to 11.0.0-beta.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index a857b8c04..c57b59b16 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "blockly", - "version": "10.3.0", + "version": "11.0.0-beta.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "blockly", - "version": "10.3.0", + "version": "11.0.0-beta.0", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index aaf3fad2a..8f9d64396 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "blockly", - "version": "10.3.0", + "version": "11.0.0-beta.0", "description": "Blockly is a library for building visual programming editors.", "keywords": [ "blockly" From 4cc4bd573cd23297365ec16e791d3bc9534497e2 Mon Sep 17 00:00:00 2001 From: Christopher Allen Date: Mon, 8 Jan 2024 17:10:47 +0000 Subject: [PATCH 03/92] fix(generators): Fix generator type declarations (#7750) * test(generators): Add generator TS import/use tests * fix(generators): Fix generator type declarations Add a missing ./ prefix to the imports in the generator wrapper .d.ts files. Fixes: #7741 --- tests/typescript/src/generators/dart.ts | 24 ++++++++++++++++ tests/typescript/src/generators/javascript.ts | 28 +++++++++++++++++++ tests/typescript/src/generators/lua.ts | 24 ++++++++++++++++ tests/typescript/src/generators/php.ts | 24 ++++++++++++++++ tests/typescript/src/generators/python.ts | 24 ++++++++++++++++ typings/dart.d.ts | 2 +- typings/javascript.d.ts | 2 +- typings/lua.d.ts | 2 +- typings/php.d.ts | 2 +- typings/python.d.ts | 2 +- 10 files changed, 129 insertions(+), 5 deletions(-) create mode 100644 tests/typescript/src/generators/dart.ts create mode 100644 tests/typescript/src/generators/javascript.ts create mode 100644 tests/typescript/src/generators/lua.ts create mode 100644 tests/typescript/src/generators/php.ts create mode 100644 tests/typescript/src/generators/python.ts diff --git a/tests/typescript/src/generators/dart.ts b/tests/typescript/src/generators/dart.ts new file mode 100644 index 000000000..ae285e5e8 --- /dev/null +++ b/tests/typescript/src/generators/dart.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright 2024 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as Blockly from 'blockly-test/core'; + +/** + * Test: should be able to import a generator instance, class, and + * Order enum. + */ +import {dartGenerator, DartGenerator, Order} from 'blockly-test/dart'; + +/** + * Test: should be able to create a simple block generator function, + * correctly typed, and insert it into the .forBlock dictionary. + */ +dartGenerator.forBlock['the_answer'] = function ( + _block: Blockly.Block, + _generator: DartGenerator, +): [string, Order] { + return ['42', Order.ATOMIC]; +}; diff --git a/tests/typescript/src/generators/javascript.ts b/tests/typescript/src/generators/javascript.ts new file mode 100644 index 000000000..716c91459 --- /dev/null +++ b/tests/typescript/src/generators/javascript.ts @@ -0,0 +1,28 @@ +/** + * @license + * Copyright 2024 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as Blockly from 'blockly-test/core'; + +/** + * Test: should be able to import a generator instance, class, and + * Order enum. + */ +import { + javascriptGenerator, + JavascriptGenerator, + Order, +} from 'blockly-test/javascript'; + +/** + * Test: should be able to create a simple block generator function, + * correctly typed, and insert it into the .forBlock dictionary. + */ +javascriptGenerator.forBlock['the_answer'] = function ( + _block: Blockly.Block, + _generator: JavascriptGenerator, +): [string, Order] { + return ['42', Order.ATOMIC]; +}; diff --git a/tests/typescript/src/generators/lua.ts b/tests/typescript/src/generators/lua.ts new file mode 100644 index 000000000..b030ff7a7 --- /dev/null +++ b/tests/typescript/src/generators/lua.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright 2024 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as Blockly from 'blockly-test/core'; + +/** + * Test: should be able to import a generator instance, class, and + * Order enum. + */ +import {luaGenerator, LuaGenerator, Order} from 'blockly-test/lua'; + +/** + * Test: should be able to create a simple block generator function, + * correctly typed, and insert it into the .forBlock dictionary. + */ +luaGenerator.forBlock['the_answer'] = function ( + _block: Blockly.Block, + _generator: LuaGenerator, +): [string, Order] { + return ['42', Order.ATOMIC]; +}; diff --git a/tests/typescript/src/generators/php.ts b/tests/typescript/src/generators/php.ts new file mode 100644 index 000000000..cb9241b43 --- /dev/null +++ b/tests/typescript/src/generators/php.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright 2024 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as Blockly from 'blockly-test/core'; + +/** + * Test: should be able to import a generator instance, class, and + * Order enum. + */ +import {phpGenerator, PhpGenerator, Order} from 'blockly-test/php'; + +/** + * Test: should be able to create a simple block generator function, + * correctly typed, and insert it into the .forBlock dictionary. + */ +phpGenerator.forBlock['the_answer'] = function ( + _block: Blockly.Block, + _generator: PhpGenerator, +): [string, Order] { + return ['42', Order.ATOMIC]; +}; diff --git a/tests/typescript/src/generators/python.ts b/tests/typescript/src/generators/python.ts new file mode 100644 index 000000000..aa1c19c38 --- /dev/null +++ b/tests/typescript/src/generators/python.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright 2024 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as Blockly from 'blockly-test/core'; + +/** + * Test: should be able to import a generator instance, class, and + * Order enum. + */ +import {pythonGenerator, PythonGenerator, Order} from 'blockly-test/python'; + +/** + * Test: should be able to create a simple block generator function, + * correctly typed, and insert it into the .forBlock dictionary. + */ +pythonGenerator.forBlock['the_answer'] = function ( + _block: Blockly.Block, + _generator: PythonGenerator, +): [string, Order] { + return ['42', Order.ATOMIC]; +}; diff --git a/typings/dart.d.ts b/typings/dart.d.ts index c2e6ccc90..0f6b1e090 100644 --- a/typings/dart.d.ts +++ b/typings/dart.d.ts @@ -4,4 +4,4 @@ * SPDX-License-Identifier: Apache-2.0 */ -export * from 'generators/dart'; +export * from './generators/dart'; diff --git a/typings/javascript.d.ts b/typings/javascript.d.ts index e71275f64..e5558381d 100644 --- a/typings/javascript.d.ts +++ b/typings/javascript.d.ts @@ -4,4 +4,4 @@ * SPDX-License-Identifier: Apache-2.0 */ -export * from 'generators/javascript'; +export * from './generators/javascript'; diff --git a/typings/lua.d.ts b/typings/lua.d.ts index 444b22fc1..6443fe90b 100644 --- a/typings/lua.d.ts +++ b/typings/lua.d.ts @@ -4,4 +4,4 @@ * SPDX-License-Identifier: Apache-2.0 */ -export * from 'generators/lua'; +export * from './generators/lua'; diff --git a/typings/php.d.ts b/typings/php.d.ts index 6e907926c..96810bc30 100644 --- a/typings/php.d.ts +++ b/typings/php.d.ts @@ -4,4 +4,4 @@ * SPDX-License-Identifier: Apache-2.0 */ -export * from 'generators/php'; +export * from './generators/php'; diff --git a/typings/python.d.ts b/typings/python.d.ts index ff6cc4f9f..fd1e3c677 100644 --- a/typings/python.d.ts +++ b/typings/python.d.ts @@ -4,4 +4,4 @@ * SPDX-License-Identifier: Apache-2.0 */ -export * from 'generators/python'; +export * from './generators/python'; From c1a9d318687ab551949022352e6efac451c16277 Mon Sep 17 00:00:00 2001 From: Christopher Allen Date: Mon, 8 Jan 2024 18:36:19 +0000 Subject: [PATCH 04/92] release: Update version number to 11.0.0-beta.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index c57b59b16..d4d803d42 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "blockly", - "version": "11.0.0-beta.0", + "version": "11.0.0-beta.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "blockly", - "version": "11.0.0-beta.0", + "version": "11.0.0-beta.1", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 8f9d64396..2d0729883 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "blockly", - "version": "11.0.0-beta.0", + "version": "11.0.0-beta.1", "description": "Blockly is a library for building visual programming editors.", "keywords": [ "blockly" From 7ef47d142aab0d797c4bb05c6ae57a7366df94f7 Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Mon, 8 Jan 2024 13:54:15 -0800 Subject: [PATCH 05/92] chore(dependency)!: Bump JSDOM to v23, dropping node 16. (#7729) * Revert "Revert "chore(deps): Bump jsdom from 22.1.0 to 23.0.0 (#7667)" (#7694)" This reverts commit f0c3f33a960f31f51f36568e773b3fbadc350921. * chore: add engine --- package-lock.json | 588 ++++++++++++++++++++++++++-------------------- package.json | 5 +- 2 files changed, 332 insertions(+), 261 deletions(-) diff --git a/package-lock.json b/package-lock.json index d4d803d42..0a36ffad4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "jsdom": "22.1.0" + "jsdom": "23.0.0" }, "devDependencies": { "@blockly/block-test": "^5.0.0", @@ -885,6 +885,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "peer": true, "engines": { "node": ">= 10" } @@ -1519,18 +1521,6 @@ "node": ">=16.3.0" } }, - "node_modules/@wdio/utils/node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", - "dev": true, - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/@wdio/utils/node_modules/decamelize": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.0.tgz", @@ -1543,32 +1533,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/utils/node_modules/http-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", - "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", - "dev": true, - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/utils/node_modules/https-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", - "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", - "dev": true, - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/@wdio/utils/node_modules/lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", @@ -1606,7 +1570,10 @@ "node_modules/abab": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==" + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "deprecated": "Use your platform's native atob() and btoa() methods instead", + "dev": true, + "peer": true }, "node_modules/acorn": { "version": "8.11.2", @@ -1630,14 +1597,14 @@ } }, "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", "dependencies": { - "debug": "4" + "debug": "^4.3.4" }, "engines": { - "node": ">= 6.0.0" + "node": ">= 14" } }, "node_modules/ajv": { @@ -2276,6 +2243,166 @@ "jsdom": "22.1.0" } }, + "node_modules/blockly/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "peer": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/blockly/node_modules/data-urls": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-4.0.0.tgz", + "integrity": "sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g==", + "dev": true, + "peer": true, + "dependencies": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^12.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/blockly/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "peer": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/blockly/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "peer": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/blockly/node_modules/jsdom": { + "version": "22.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-22.1.0.tgz", + "integrity": "sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw==", + "dev": true, + "peer": true, + "dependencies": { + "abab": "^2.0.6", + "cssstyle": "^3.0.0", + "data-urls": "^4.0.0", + "decimal.js": "^10.4.3", + "domexception": "^4.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.4", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.6.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^12.0.1", + "ws": "^8.13.0", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/blockly/node_modules/tr46": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", + "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "dev": true, + "peer": true, + "dependencies": { + "punycode": "^2.3.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/blockly/node_modules/w3c-xmlserializer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "dev": true, + "peer": true, + "dependencies": { + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/blockly/node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/blockly/node_modules/whatwg-url": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-12.0.1.tgz", + "integrity": "sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==", + "dev": true, + "peer": true, + "dependencies": { + "tr46": "^4.1.1", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/blockly/node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=12" + } + }, "node_modules/bluebird": { "version": "3.4.7", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", @@ -3090,6 +3217,17 @@ "node": ">=0.10.0" } }, + "node_modules/cssstyle": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-3.0.0.tgz", + "integrity": "sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==", + "dependencies": { + "rrweb-cssom": "^0.6.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/d": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", @@ -3115,6 +3253,41 @@ "node": ">= 14" } }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-urls/node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz", + "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/date-fns": { "version": "2.30.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", @@ -3382,6 +3555,9 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "peer": true, "dependencies": { "webidl-conversions": "^7.0.0" }, @@ -4744,18 +4920,6 @@ "node": "^16.13 || >=18 || >=20" } }, - "node_modules/geckodriver/node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", - "dev": true, - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/geckodriver/node_modules/data-uri-to-buffer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", @@ -4777,32 +4941,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/geckodriver/node_modules/http-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", - "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", - "dev": true, - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/geckodriver/node_modules/https-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", - "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", - "dev": true, - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/geckodriver/node_modules/isexe": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", @@ -6248,6 +6386,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, "dependencies": { "whatwg-encoding": "^2.0.0" }, @@ -6276,16 +6415,15 @@ } }, "node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", + "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/http-server": { @@ -6329,15 +6467,26 @@ } }, "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", "dependencies": { - "agent-base": "6", + "agent-base": "^7.0.2", "debug": "4" }, "engines": { - "node": ">= 6" + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, "node_modules/ieee754": { @@ -6874,39 +7023,37 @@ } }, "node_modules/jsdom": { - "version": "22.1.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-22.1.0.tgz", - "integrity": "sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw==", + "version": "23.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-23.0.0.tgz", + "integrity": "sha512-cbL/UCtohJguhFC7c2/hgW6BeZCNvP7URQGnx9tSJRYKCdnfbfWOrtuLTMfiB2VxKsx5wPHVsh/J0aBy9lIIhQ==", "dependencies": { - "abab": "^2.0.6", "cssstyle": "^3.0.0", - "data-urls": "^4.0.0", + "data-urls": "^5.0.0", "decimal.js": "^10.4.3", - "domexception": "^4.0.0", "form-data": "^4.0.0", - "html-encoding-sniffer": "^3.0.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.1", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.4", + "nwsapi": "^2.2.7", "parse5": "^7.1.2", "rrweb-cssom": "^0.6.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^4.1.2", - "w3c-xmlserializer": "^4.0.0", + "tough-cookie": "^4.1.3", + "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^2.0.0", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^12.0.1", - "ws": "^8.13.0", - "xml-name-validator": "^4.0.0" + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.14.2", + "xml-name-validator": "^5.0.0" }, "engines": { - "node": ">=16" + "node": ">=18" }, "peerDependencies": { - "canvas": "^2.5.0" + "canvas": "^3.0.0" }, "peerDependenciesMeta": { "canvas": { @@ -6914,51 +7061,69 @@ } } }, - "node_modules/jsdom/node_modules/cssstyle": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-3.0.0.tgz", - "integrity": "sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==", - "dependencies": { - "rrweb-cssom": "^0.6.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/jsdom/node_modules/data-urls": { + "node_modules/jsdom/node_modules/html-encoding-sniffer": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-4.0.0.tgz", - "integrity": "sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g==", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", "dependencies": { - "abab": "^2.0.6", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^12.0.0" + "whatwg-encoding": "^3.1.1" }, "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/jsdom/node_modules/tr46": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", - "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", "dependencies": { - "punycode": "^2.3.0" + "punycode": "^2.3.1" }, "engines": { - "node": ">=14" + "node": ">=18" + } + }, + "node_modules/jsdom/node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" } }, "node_modules/jsdom/node_modules/whatwg-url": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-12.0.1.tgz", - "integrity": "sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz", + "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==", "dependencies": { - "tr46": "^4.1.1", + "tr46": "^5.0.0", "webidl-conversions": "^7.0.0" }, "engines": { - "node": ">=14" + "node": ">=18" + } + }, + "node_modules/jsdom/node_modules/ws": { + "version": "8.14.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", + "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, "node_modules/json-buffer": { @@ -8090,9 +8255,9 @@ } }, "node_modules/nwsapi": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.5.tgz", - "integrity": "sha512-6xpotnECFy/og7tKSBVmUNft7J3jyXAka4XvG6AUhFWRz+Q/Ljus7znJAA3bxColfQLdS+XsjoodtJfCgeTEFQ==" + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", + "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==" }, "node_modules/object-assign": { "version": "4.1.1", @@ -8434,44 +8599,6 @@ "node": ">= 14" } }, - "node_modules/pac-proxy-agent/node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", - "dev": true, - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/pac-proxy-agent/node_modules/http-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", - "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", - "dev": true, - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/pac-proxy-agent/node_modules/https-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", - "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", - "dev": true, - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/pac-resolver": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.0.tgz", @@ -8974,44 +9101,6 @@ "node": ">= 14" } }, - "node_modules/proxy-agent/node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", - "dev": true, - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/proxy-agent/node_modules/http-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", - "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", - "dev": true, - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/proxy-agent/node_modules/https-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", - "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", - "dev": true, - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/proxy-agent/node_modules/lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", @@ -9064,9 +9153,9 @@ } }, "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "engines": { "node": ">=6" } @@ -10052,18 +10141,6 @@ "node": ">= 14" } }, - "node_modules/socks-proxy-agent/node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", - "dev": true, - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/socks/node_modules/ip": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", @@ -11274,14 +11351,14 @@ } }, "node_modules/w3c-xmlserializer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", - "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", "dependencies": { - "xml-name-validator": "^4.0.0" + "xml-name-validator": "^5.0.0" }, "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/wait-port": { @@ -11460,6 +11537,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, "dependencies": { "iconv-lite": "0.6.3" }, @@ -11467,23 +11545,12 @@ "node": ">=12" } }, - "node_modules/whatwg-encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/whatwg-mimetype": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", - "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/whatwg-url": { @@ -11574,6 +11641,7 @@ "version": "8.13.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "dev": true, "engines": { "node": ">=10.0.0" }, @@ -11591,11 +11659,11 @@ } }, "node_modules/xml-name-validator": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", - "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/xmlchars": { diff --git a/package.json b/package.json index 2d0729883..d93dc6ca4 100644 --- a/package.json +++ b/package.json @@ -105,6 +105,9 @@ "yargs": "^17.2.1" }, "dependencies": { - "jsdom": "22.1.0" + "jsdom": "23.0.0" + }, + "engines": { + "node": ">=18" } } From 0ad0adfb75ce5a2d183f86a9636b9a85ddd3be64 Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Mon, 8 Jan 2024 14:00:12 -0800 Subject: [PATCH 06/92] feat!: add serialization hooks to procedure models (#7740) * feat!: add serialization hooks to procedure models * chore: fix tests * chore: remove internal functions * fix: add state interfaces back --- core/interfaces/i_parameter_model.ts | 8 + core/interfaces/i_procedure_model.ts | 8 + core/serialization/procedures.ts | 103 +++---- tests/mocha/jso_deserialization_test.js | 347 +++--------------------- tests/mocha/jso_serialization_test.js | 123 ++------- tests/mocha/test_helpers/procedures.js | 16 ++ 6 files changed, 136 insertions(+), 469 deletions(-) diff --git a/core/interfaces/i_parameter_model.ts b/core/interfaces/i_parameter_model.ts index 1b0239703..6b351b6b3 100644 --- a/core/interfaces/i_parameter_model.ts +++ b/core/interfaces/i_parameter_model.ts @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import {ParameterState} from '../serialization/procedures'; import {IProcedureModel} from './i_procedure_model'; /** @@ -40,4 +41,11 @@ export interface IParameterModel { /** Sets the procedure model this parameter is associated with. */ setProcedureModel(model: IProcedureModel): this; + + /** + * Serializes the state of the parameter to JSON. + * + * @returns JSON serializable state of the parameter. + */ + saveState(): ParameterState; } diff --git a/core/interfaces/i_procedure_model.ts b/core/interfaces/i_procedure_model.ts index cb5fda09f..61026adae 100644 --- a/core/interfaces/i_procedure_model.ts +++ b/core/interfaces/i_procedure_model.ts @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import {State} from '../serialization/procedures.js'; import {IParameterModel} from './i_parameter_model.js'; /** @@ -60,4 +61,11 @@ export interface IProcedureModel { * disabled, all procedure caller blocks should be disabled as well. */ getEnabled(): boolean; + + /** + * Serializes the state of the procedure to JSON. + * + * @returns JSON serializable state of the procedure. + */ + saveState(): State; } diff --git a/core/serialization/procedures.ts b/core/serialization/procedures.ts index 34d381171..688904b08 100644 --- a/core/serialization/procedures.ts +++ b/core/serialization/procedures.ts @@ -10,89 +10,77 @@ import type {ISerializer} from '../interfaces/i_serializer.js'; import * as priorities from './priorities.js'; import type {Workspace} from '../workspace.js'; -/** - * Representation of a procedure data model. - */ +/** Represents the state of a procedure model. */ export interface State { - // TODO: This should also handle enabled. id: string; name: string; returnTypes: string[] | null; parameters?: ParameterState[]; + [key: string]: unknown; } -/** - * Representation of a parameter data model. - */ +/** Represents the state of a parameter model. */ export interface ParameterState { id: string; name: string; types?: string[]; + [key: string]: unknown; } /** * A newable signature for an IProcedureModel. * * Refer to - * https://www.typescriptlang.org/docs/handbook/2/generics.html#using-class-types-in-generics + * https://www.typescriptlang.org/docs/handbook/interfaces.html#difference-between-the-static-and-instance-sides-of-classes * for what is going on with this. */ -type ProcedureModelConstructor = new ( - workspace: Workspace, - name: string, - id: string, -) => ProcedureModel; +interface ProcedureModelConstructor { + new (workspace: Workspace, name: string, id: string): ProcedureModel; + + /** + * Deserializes the JSON state and returns a procedure model. + * + * @param state The state to deserialize. + * @param workspace The workspace to load the procedure model into. + * @returns The constructed procedure model. + */ + loadState(state: Object, workspace: Workspace): ProcedureModel; +} /** * A newable signature for an IParameterModel. * * Refer to - * https://www.typescriptlang.org/docs/handbook/2/generics.html#using-class-types-in-generics + * https://www.typescriptlang.org/docs/handbook/interfaces.html#difference-between-the-static-and-instance-sides-of-classes * for what is going on with this. */ -type ParameterModelConstructor = new ( - workspace: Workspace, - name: string, - id: string, -) => ParameterModel; +interface ParameterModelConstructor { + new (workspace: Workspace, name: string, id: string): ParameterModel; -/** - * Serializes the given IProcedureModel to JSON. - * - * @internal - */ -export function saveProcedure(proc: IProcedureModel): State { - const state: State = { - id: proc.getId(), - name: proc.getName(), - returnTypes: proc.getReturnTypes(), - }; - if (!proc.getParameters().length) return state; - state.parameters = proc.getParameters().map((param) => saveParameter(param)); - return state; + /** + * Deserializes the JSON state and returns a parameter model. + * + * @param state The state to deserialize. + * @param workspace The workspace to load the parameter model into. + * @returns The constructed parameter model. + */ + loadState(state: Object, workspace: Workspace): ParameterModel; } /** - * Serializes the given IParameterModel to JSON. - * - * @internal + * Serializes the given IProcedureModel to JSON. */ -export function saveParameter(param: IParameterModel): ParameterState { - const state: ParameterState = { - id: param.getId(), - name: param.getName(), - }; - if (!param.getTypes().length) return state; - state.types = param.getTypes(); +function saveProcedure(proc: IProcedureModel): State { + const state: State = proc.saveState(); + if (!proc.getParameters().length) return state; + state.parameters = proc.getParameters().map((param) => param.saveState()); return state; } /** * Deserializes the given procedure model State from JSON. - * - * @internal */ -export function loadProcedure< +function loadProcedure< ProcedureModel extends IProcedureModel, ParameterModel extends IParameterModel, >( @@ -101,36 +89,17 @@ export function loadProcedure< state: State, workspace: Workspace, ): ProcedureModel { - const proc = new procedureModelClass( - workspace, - state.name, - state.id, - ).setReturnTypes(state.returnTypes); + const proc = procedureModelClass.loadState(state, workspace); if (!state.parameters) return proc; for (const [index, param] of state.parameters.entries()) { proc.insertParameter( - loadParameter(parameterModelClass, param, workspace), + parameterModelClass.loadState(param, workspace), index, ); } return proc; } -/** - * Deserializes the given ParameterState from JSON. - * - * @internal - */ -export function loadParameter( - parameterModelClass: ParameterModelConstructor, - state: ParameterState, - workspace: Workspace, -): ParameterModel { - const model = new parameterModelClass(workspace, state.name, state.id); - if (state.types) model.setTypes(state.types); - return model; -} - /** Serializer for saving and loading procedure state. */ export class ProcedureSerializer< ProcedureModel extends IProcedureModel, diff --git a/tests/mocha/jso_deserialization_test.js b/tests/mocha/jso_deserialization_test.js index d58e208fb..7c8a06db8 100644 --- a/tests/mocha/jso_deserialization_test.js +++ b/tests/mocha/jso_deserialization_test.js @@ -11,14 +11,20 @@ import { } from './test_helpers/setup_teardown.js'; import {assertEventFired} from './test_helpers/events.js'; import * as eventUtils from '../../build/src/core/events/utils.js'; +import { + MockParameterModel, + MockProcedureModel, +} from './test_helpers/procedures.js'; suite('JSO Deserialization', function () { setup(function () { sharedTestSetup.call(this); + this.sandbox = sinon.createSandbox(); this.workspace = new Blockly.Workspace(); }); teardown(function () { + this.sandbox.restore(); sharedTestTeardown.call(this); }); @@ -785,95 +791,6 @@ suite('JSO Deserialization', function () { }); suite('Procedures', function () { - class MockProcedureModel { - constructor(workspace, name, id) { - this.id = id ?? Blockly.utils.idGenerator.genUid(); - this.name = name; - this.parameters = []; - this.returnTypes = null; - this.enabled = true; - } - - setName(name) { - this.name = name; - return this; - } - - insertParameter(parameterModel, index) { - this.parameters.splice(index, 0, parameterModel); - return this; - } - - deleteParameter(index) { - this.parameters.splice(index, 1); - return this; - } - - setReturnTypes(types) { - this.returnTypes = types; - return this; - } - - setEnabled(enabled) { - this.enabled = enabled; - return this; - } - - getId() { - return this.id; - } - - getName() { - return this.name; - } - - getParameter(index) { - return this.parameters[index]; - } - - getParameters() { - return [...this.parameters]; - } - - getReturnTypes() { - return this.returnTypes; - } - - getEnabled() { - return this.enabled; - } - } - - class MockParameterModel { - constructor(workspace, name, id) { - this.id = id ?? Blockly.utils.idGenerator.genUid(); - this.name = name; - this.types = []; - } - - setName(name) { - this.name = name; - return this; - } - - setTypes(types) { - this.types = types; - return this; - } - - getName() { - return this.name; - } - - getTypes() { - return this.types; - } - - getId() { - return this.id; - } - } - setup(function () { this.procedureSerializer = new Blockly.serialization.procedures.ProcedureSerializer( @@ -888,232 +805,46 @@ suite('JSO Deserialization', function () { this.procedureMap = null; }); - suite('invariant properties', function () { - test('the id property is assigned', function () { - const jso = { - 'id': 'test id', - 'name': 'test name', - 'returnTypes': [], - }; + test('load is called for the procedure model', function () { + const state = [ + { + 'id': 'test', + 'parameters': [], + }, + ]; + const spy = this.sandbox.spy(MockProcedureModel, 'loadState'); - this.procedureSerializer.load([jso], this.workspace); + this.procedureSerializer.load(state, this.workspace); - const procedureModel = this.procedureMap.getProcedures()[0]; - chai.assert.isNotNull( - procedureModel, - 'Expected a procedure model to exist', - ); - chai.assert.equal( - procedureModel.getId(), - 'test id', - 'Expected the procedure model ID to match the serialized ID', - ); - }); - - test('the name property is assigned', function () { - const jso = { - 'id': 'test id', - 'name': 'test name', - 'returnTypes': [], - }; - - this.procedureSerializer.load([jso], this.workspace); - - const procedureModel = this.procedureMap.getProcedures()[0]; - chai.assert.isNotNull( - procedureModel, - 'Expected a procedure model to exist', - ); - chai.assert.equal( - procedureModel.getName(), - 'test name', - 'Expected the procedure model name to match the serialized name', - ); - }); + chai.assert.isTrue( + spy.calledOnce, + 'Expected the loadState method to be called', + ); }); - suite('return types', function () { - test('if the return type property is null it is assigned', function () { - const jso = { - 'id': 'test id', - 'name': 'test name', - 'returnTypes': null, - }; + test('load is called for each parameter model', function () { + const state = [ + { + 'id': 'test', + 'parameters': [ + { + 'id': 'test1', + }, + { + 'id': 'test2', + }, + ], + }, + ]; - this.procedureSerializer.load([jso], this.workspace); + const spy = this.sandbox.spy(MockParameterModel, 'loadState'); - const procedureModel = this.procedureMap.getProcedures()[0]; - chai.assert.isNotNull( - procedureModel, - 'Expected a procedure model to exist', - ); - chai.assert.isNull( - procedureModel.getReturnTypes(), - 'Expected the procedure model types to be null', - ); - }); + this.procedureSerializer.load(state, this.workspace); - test('if the return type property is an empty array it is assigned', function () { - const jso = { - 'id': 'test id', - 'name': 'test name', - 'returnTypes': [], - }; - - this.procedureSerializer.load([jso], this.workspace); - - const procedureModel = this.procedureMap.getProcedures()[0]; - chai.assert.isNotNull( - procedureModel, - 'Expected a procedure model to exist', - ); - chai.assert.isArray( - procedureModel.getReturnTypes(), - 'Expected the procedure model types to be an array', - ); - chai.assert.isEmpty( - procedureModel.getReturnTypes(), - 'Expected the procedure model types array to be empty', - ); - }); - - test('if the return type property is a string array it is assigned', function () { - const jso = { - 'id': 'test id', - 'name': 'test name', - 'returnTypes': ['test type 1', 'test type 2'], - }; - - this.procedureSerializer.load([jso], this.workspace); - - const procedureModel = this.procedureMap.getProcedures()[0]; - chai.assert.isNotNull( - procedureModel, - 'Expected a procedure model to exist', - ); - chai.assert.isArray( - procedureModel.getReturnTypes(), - 'Expected the procedure model types to be an array', - ); - chai.assert.deepEqual( - procedureModel.getReturnTypes(), - ['test type 1', 'test type 2'], - 'Expected the procedure model types array to be match the ' + - 'serialized array', - ); - }); - }); - - suite('parameters', function () { - suite('invariant properties', function () { - test('the id property is assigned', function () { - const jso = { - 'id': 'test id', - 'name': 'test name', - 'returnTypes': [], - 'parameters': [ - { - 'id': 'test id', - 'name': 'test name', - }, - ], - }; - - this.procedureSerializer.load([jso], this.workspace); - - const parameterModel = this.procedureMap - .getProcedures()[0] - .getParameters()[0]; - chai.assert.isNotNull( - parameterModel, - 'Expected a parameter model to exist', - ); - chai.assert.equal( - parameterModel.getId(), - 'test id', - 'Expected the parameter model ID to match the serialized ID', - ); - }); - - test('the name property is assigned', function () { - const jso = { - 'id': 'test id', - 'name': 'test name', - 'returnTypes': [], - 'parameters': [ - { - 'id': 'test id', - 'name': 'test name', - }, - ], - }; - - this.procedureSerializer.load([jso], this.workspace); - - const parameterModel = this.procedureMap - .getProcedures()[0] - .getParameters()[0]; - chai.assert.isNotNull( - parameterModel, - 'Expected a parameter model to exist', - ); - chai.assert.equal( - parameterModel.getName(), - 'test name', - 'Expected the parameter model name to match the serialized name', - ); - }); - }); - - suite('types', function () { - test('if the type property does not exist, nothing is assigned', function () { - const jso = { - 'id': 'test id', - 'name': 'test name', - 'returnTypes': [], - 'parameters': [ - { - 'id': 'test id', - 'name': 'test name', - }, - ], - }; - - chai.assert.doesNotThrow(() => { - this.procedureMap.getProcedures()[0].getParameters()[0]; - }, 'Expected the deserializer to skip the non-existant type property'); - }); - - test('if the type property exists, it is assigned', function () { - const jso = { - 'id': 'test id', - 'name': 'test name', - 'returnTypes': [], - 'parameters': [ - { - 'id': 'test id', - 'name': 'test name', - 'types': ['test type 1', 'test type 2'], - }, - ], - }; - - this.procedureSerializer.load([jso], this.workspace); - - const parameterModel = this.procedureMap - .getProcedures()[0] - .getParameters()[0]; - chai.assert.isNotNull( - parameterModel, - 'Expected a parameter model to exist', - ); - chai.assert.deepEqual( - parameterModel.getTypes(), - ['test type 1', 'test type 2'], - 'Expected the parameter model types to match the serialized types', - ); - }); - }); + chai.assert.isTrue( + spy.calledTwice, + 'Expected the loadState method to be called once for each parameter', + ); }); }); }); diff --git a/tests/mocha/jso_serialization_test.js b/tests/mocha/jso_serialization_test.js index 895ff0be2..04bc65b2e 100644 --- a/tests/mocha/jso_serialization_test.js +++ b/tests/mocha/jso_serialization_test.js @@ -25,6 +25,7 @@ suite('JSO Serialization', function () { setup(function () { sharedTestSetup.call(this); this.workspace = new Blockly.Workspace(); + this.sandbox = sinon.createSandbox(); defineStackBlock(); defineRowBlock(); @@ -34,6 +35,7 @@ suite('JSO Serialization', function () { }); teardown(function () { + this.sandbox.restore(); workspaceTeardown.call(this, this.workspace); sharedTestTeardown.call(this); }); @@ -857,106 +859,39 @@ suite('JSO Serialization', function () { this.serializer = null; }); - suite('invariant properties', function () { - test('the state always has an id property', function () { - const procedureModel = new MockProcedureModel(); - this.procedureMap.add(procedureModel); - const jso = this.serializer.save(this.workspace); - const procedure = jso[0]; - assertProperty(procedure, 'id', procedureModel.getId()); - }); + test('save is called on the procedure model', function () { + const proc = new MockProcedureModel(); + this.workspace.getProcedureMap().set('test', proc); + const spy = this.sandbox.spy(proc, 'saveState'); - test('if the name has not been set, name is an empty string', function () { - const procedureModel = new MockProcedureModel(); - this.procedureMap.add(procedureModel); - const jso = this.serializer.save(this.workspace); - const procedure = jso[0]; - assertProperty(procedure, 'name', ''); - }); + this.serializer.save(this.workspace); - test('if the name has been set, name is the string', function () { - const procedureModel = new MockProcedureModel().setName('testName'); - this.procedureMap.add(procedureModel); - const jso = this.serializer.save(this.workspace); - const procedure = jso[0]; - assertProperty(procedure, 'name', 'testName'); - }); + chai.assert.isTrue( + spy.calledOnce, + 'Expected the saveState method to be called on the procedure model', + ); }); - suite('return types', function () { - test('if the procedure does not return, returnTypes is null', function () { - const procedureModel = new MockProcedureModel(); - this.procedureMap.add(procedureModel); - const jso = this.serializer.save(this.workspace); - const procedure = jso[0]; - assertProperty(procedure, 'returnTypes', null); - }); + test('save is called on each parameter model', function () { + const proc = new MockProcedureModel(); + const param1 = new MockParameterModel(); + const param2 = new MockParameterModel(); + proc.insertParameter(param1, 0); + proc.insertParameter(param2, 1); + this.workspace.getProcedureMap().set('test', proc); + const spy1 = this.sandbox.spy(param1, 'saveState'); + const spy2 = this.sandbox.spy(param2, 'saveState'); - test('if the procedure has no return type, returnTypes is an empty array', function () { - const procedureModel = new MockProcedureModel().setReturnTypes([]); - this.procedureMap.add(procedureModel); - const jso = this.serializer.save(this.workspace); - const procedure = jso[0]; - assertProperty(procedure, 'returnTypes', []); - }); + this.serializer.save(this.workspace); - test('if the procedure has return types, returnTypes is the array', function () { - const procedureModel = new MockProcedureModel().setReturnTypes([ - 'a type', - ]); - this.procedureMap.add(procedureModel); - const jso = this.serializer.save(this.workspace); - const procedure = jso[0]; - assertProperty(procedure, 'returnTypes', ['a type']); - }); - }); - - suite('parameters', function () { - suite('invariant properties', function () { - test('the state always has an id property', function () { - const parameterModel = new MockParameterModel('testparam'); - this.procedureMap.add( - new MockProcedureModel().insertParameter(parameterModel, 0), - ); - const jso = this.serializer.save(this.workspace); - const parameter = jso[0]['parameters'][0]; - assertProperty(parameter, 'id', parameterModel.getId()); - }); - - test('the state always has a name property', function () { - const parameterModel = new MockParameterModel('testparam'); - this.procedureMap.add( - new MockProcedureModel().insertParameter(parameterModel, 0), - ); - const jso = this.serializer.save(this.workspace); - const parameter = jso[0]['parameters'][0]; - assertProperty(parameter, 'name', 'testparam'); - }); - }); - - suite('types', function () { - test('if the parameter has no type, there is no type property', function () { - const parameterModel = new MockParameterModel('testparam'); - this.procedureMap.add( - new MockProcedureModel().insertParameter(parameterModel, 0), - ); - const jso = this.serializer.save(this.workspace); - const parameter = jso[0]['parameters'][0]; - assertNoProperty(parameter, 'types'); - }); - - test('if the parameter has types, types is an array', function () { - const parameterModel = new MockParameterModel('testparam').setTypes([ - 'a type', - ]); - this.procedureMap.add( - new MockProcedureModel().insertParameter(parameterModel, 0), - ); - const jso = this.serializer.save(this.workspace); - const parameter = jso[0]['parameters'][0]; - assertProperty(parameter, 'types', ['a type']); - }); - }); + chai.assert.isTrue( + spy1.calledOnce, + 'Expected the saveState method to be called on the first parameter model', + ); + chai.assert.isTrue( + spy2.calledOnce, + 'Expected the saveState method to be called on the first parameter model', + ); }); }); }); diff --git a/tests/mocha/test_helpers/procedures.js b/tests/mocha/test_helpers/procedures.js index e4ddc0e3f..4717b38c8 100644 --- a/tests/mocha/test_helpers/procedures.js +++ b/tests/mocha/test_helpers/procedures.js @@ -189,6 +189,14 @@ export class MockProcedureModel { this.enabled = true; } + static loadState(state, workspace) { + return new MockProcedureModel(); + } + + saveState() { + return {}; + } + setName(name) { this.name = name; return this; @@ -250,6 +258,14 @@ export class MockParameterModel { this.types = []; } + static loadState(state, workspace) { + return new MockParameterModel('test'); + } + + saveState() { + return {}; + } + setName(name) { this.name = name; return this; From b1ef6ae6013411b5ae0d8255cb8d4cefa7c3eb1f Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Mon, 8 Jan 2024 14:08:09 -0800 Subject: [PATCH 07/92] feat!: modify icons to use the rendering queue (#7743) --- core/block_svg.ts | 2 - core/icons/comment_icon.ts | 14 +-- core/icons/mutator_icon.ts | 5 +- core/icons/warning_icon.ts | 5 +- core/interfaces/i_has_bubble.ts | 2 +- tests/mocha/blocks/procedures_test.js | 136 +++++++++++++------------- tests/mocha/comment_test.js | 20 ++-- tests/mocha/mutator_test.js | 12 +-- 8 files changed, 101 insertions(+), 95 deletions(-) diff --git a/core/block_svg.ts b/core/block_svg.ts index 2655642d7..c6c9c9fa3 100644 --- a/core/block_svg.ts +++ b/core/block_svg.ts @@ -1029,7 +1029,6 @@ export class BlockSvg icon.applyColour(); icon.updateEditable(); this.queueRender(); - renderManagement.triggerQueuedRenders(); this.bumpNeighbours(); } @@ -1058,7 +1057,6 @@ export class BlockSvg if (this.rendered) { this.queueRender(); - renderManagement.triggerQueuedRenders(); this.bumpNeighbours(); } return removed; diff --git a/core/icons/comment_icon.ts b/core/icons/comment_icon.ts index 7756088b6..6042b44e2 100644 --- a/core/icons/comment_icon.ts +++ b/core/icons/comment_icon.ts @@ -22,6 +22,7 @@ import {Svg} from '../utils/svg.js'; import {TextBubble} from '../bubbles/text_bubble.js'; import {TextInputBubble} from '../bubbles/textinput_bubble.js'; import type {WorkspaceSvg} from '../workspace_svg.js'; +import * as renderManagement from '../render_management.js'; /** The size of the comment icon in workspace-scale units. */ const SIZE = 17; @@ -138,12 +139,12 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable { * Updates the state of the bubble (editable / noneditable) to reflect the * state of the bubble if the bubble is currently shown. */ - override updateEditable(): void { + override async updateEditable(): Promise { super.updateEditable(); if (this.bubbleIsVisible()) { // Close and reopen the bubble to display the correct UI. - this.setBubbleVisible(false); - this.setBubbleVisible(true); + await this.setBubbleVisible(false); + await this.setBubbleVisible(true); } } @@ -214,8 +215,7 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable { state['height'] ?? DEFAULT_BUBBLE_HEIGHT, ); this.bubbleVisiblity = state['pinned'] ?? false; - // Give the block a chance to be positioned and rendered before showing. - setTimeout(() => this.setBubbleVisible(this.bubbleVisiblity), 1); + this.setBubbleVisible(this.bubbleVisiblity); } override onClick(): void { @@ -263,7 +263,7 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable { return this.bubbleVisiblity; } - setBubbleVisible(visible: boolean): void { + async setBubbleVisible(visible: boolean): Promise { if (visible && (this.textBubble || this.textInputBubble)) return; if (!visible && !(this.textBubble || this.textInputBubble)) return; @@ -271,6 +271,8 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable { if (!this.sourceBlock.rendered || this.sourceBlock.isInFlyout) return; + await renderManagement.finishQueuedRenders(); + if (visible) { if (this.sourceBlock.isEditable()) { this.showEditableBubble(); diff --git a/core/icons/mutator_icon.ts b/core/icons/mutator_icon.ts index 56365ed1f..f4c1ea83d 100644 --- a/core/icons/mutator_icon.ts +++ b/core/icons/mutator_icon.ts @@ -24,6 +24,7 @@ import {Svg} from '../utils/svg.js'; import type {WorkspaceSvg} from '../workspace_svg.js'; import * as deprecation from '../utils/deprecation.js'; import {IconType} from './icon_types.js'; +import * as renderManagement from '../render_management.js'; /** The size of the mutator icon in workspace-scale units. */ const SIZE = 17; @@ -165,9 +166,11 @@ export class MutatorIcon extends Icon implements IHasBubble { return !!this.miniWorkspaceBubble; } - setBubbleVisible(visible: boolean): void { + async setBubbleVisible(visible: boolean): Promise { if (this.bubbleIsVisible() === visible) return; + await renderManagement.finishQueuedRenders(); + if (visible) { this.miniWorkspaceBubble = new MiniWorkspaceBubble( this.getMiniWorkspaceConfig(), diff --git a/core/icons/warning_icon.ts b/core/icons/warning_icon.ts index f0862c3e9..08f511a60 100644 --- a/core/icons/warning_icon.ts +++ b/core/icons/warning_icon.ts @@ -17,6 +17,7 @@ import {Size} from '../utils.js'; import {Svg} from '../utils/svg.js'; import {TextBubble} from '../bubbles/text_bubble.js'; import {IconType} from './icon_types.js'; +import * as renderManagement from '../render_management.js'; /** The size of the warning icon in workspace-scale units. */ const SIZE = 17; @@ -168,9 +169,11 @@ export class WarningIcon extends Icon implements IHasBubble { return !!this.textBubble; } - setBubbleVisible(visible: boolean): void { + async setBubbleVisible(visible: boolean): Promise { if (this.bubbleIsVisible() === visible) return; + await renderManagement.finishQueuedRenders(); + if (visible) { this.textBubble = new TextBubble( this.getText(), diff --git a/core/interfaces/i_has_bubble.ts b/core/interfaces/i_has_bubble.ts index a2ba6093a..276feff21 100644 --- a/core/interfaces/i_has_bubble.ts +++ b/core/interfaces/i_has_bubble.ts @@ -9,7 +9,7 @@ export interface IHasBubble { bubbleIsVisible(): boolean; /** Sets whether the bubble is open or not. */ - setBubbleVisible(visible: boolean): void; + setBubbleVisible(visible: boolean): Promise; } /** Type guard that checks whether the given object is a IHasBubble. */ diff --git a/tests/mocha/blocks/procedures_test.js b/tests/mocha/blocks/procedures_test.js index 6173179cd..f9d027c99 100644 --- a/tests/mocha/blocks/procedures_test.js +++ b/tests/mocha/blocks/procedures_test.js @@ -86,10 +86,10 @@ suite('Procedures', function () { }); suite('adding procedure parameters', function () { - test('the mutator flyout updates to avoid parameter name conflicts', function () { + test('the mutator flyout updates to avoid parameter name conflicts', async function () { const defBlock = createProcDefBlock(this.workspace); const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE); - mutatorIcon.setBubbleVisible(true); + await mutatorIcon.setBubbleVisible(true); const mutatorWorkspace = mutatorIcon.getWorkspace(); const origFlyoutParamName = mutatorWorkspace .getFlyout() @@ -119,11 +119,11 @@ suite('Procedures', function () { ); }); - test('adding a parameter to the procedure updates procedure defs', function () { + test('adding a parameter to the procedure updates procedure defs', async function () { // Create a stack of container, parameter. const defBlock = createProcDefBlock(this.workspace); const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE); - mutatorIcon.setBubbleVisible(true); + await mutatorIcon.setBubbleVisible(true); const mutatorWorkspace = mutatorIcon.getWorkspace(); const containerBlock = mutatorWorkspace.getTopBlocks()[0]; const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg'); @@ -143,12 +143,12 @@ suite('Procedures', function () { ); }); - test('adding a parameter to the procedure updates procedure callers', function () { + test('adding a parameter to the procedure updates procedure callers', async function () { // Create a stack of container, parameter. const defBlock = createProcDefBlock(this.workspace); const callBlock = createProcCallBlock(this.workspace); const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE); - mutatorIcon.setBubbleVisible(true); + await mutatorIcon.setBubbleVisible(true); const mutatorWorkspace = mutatorIcon.getWorkspace(); const containerBlock = mutatorWorkspace.getTopBlocks()[0]; const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg'); @@ -169,11 +169,11 @@ suite('Procedures', function () { ); }); - test('undoing adding a procedure parameter removes it', function () { + test('undoing adding a procedure parameter removes it', async function () { // Create a stack of container, parameter. const defBlock = createProcDefBlock(this.workspace); const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE); - mutatorIcon.setBubbleVisible(true); + await mutatorIcon.setBubbleVisible(true); const mutatorWorkspace = mutatorIcon.getWorkspace(); const containerBlock = mutatorWorkspace.getTopBlocks()[0]; const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg'); @@ -194,11 +194,11 @@ suite('Procedures', function () { test( 'undoing and redoing adding a procedure parameter maintains ' + 'the same state', - function () { + async function () { // Create a stack of container, parameter. const defBlock = createProcDefBlock(this.workspace); const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE); - mutatorIcon.setBubbleVisible(true); + await mutatorIcon.setBubbleVisible(true); const mutatorWorkspace = mutatorIcon.getWorkspace(); const containerBlock = mutatorWorkspace.getTopBlocks()[0]; const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg'); @@ -224,11 +224,11 @@ suite('Procedures', function () { }); suite('deleting procedure parameters', function () { - test('deleting a parameter from the procedure updates procedure defs', function () { + test('deleting a parameter from the procedure updates procedure defs', async function () { // Create a stack of container, parameter. const defBlock = createProcDefBlock(this.workspace); const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE); - mutatorIcon.setBubbleVisible(true); + await mutatorIcon.setBubbleVisible(true); const mutatorWorkspace = mutatorIcon.getWorkspace(); const containerBlock = mutatorWorkspace.getTopBlocks()[0]; const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg'); @@ -247,12 +247,12 @@ suite('Procedures', function () { ); }); - test('deleting a parameter from the procedure udpates procedure callers', function () { + test('deleting a parameter from the procedure udpates procedure callers', async function () { // Create a stack of container, parameter. const defBlock = createProcDefBlock(this.workspace); const callBlock = createProcCallBlock(this.workspace); const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE); - mutatorIcon.setBubbleVisible(true); + await mutatorIcon.setBubbleVisible(true); const mutatorWorkspace = mutatorIcon.getWorkspace(); const containerBlock = mutatorWorkspace.getTopBlocks()[0]; const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg'); @@ -271,11 +271,11 @@ suite('Procedures', function () { ); }); - test('undoing deleting a procedure parameter adds it', function () { + test('undoing deleting a procedure parameter adds it', async function () { // Create a stack of container, parameter. const defBlock = createProcDefBlock(this.workspace); const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE); - mutatorIcon.setBubbleVisible(true); + await mutatorIcon.setBubbleVisible(true); const mutatorWorkspace = mutatorIcon.getWorkspace(); const containerBlock = mutatorWorkspace.getTopBlocks()[0]; const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg'); @@ -298,11 +298,11 @@ suite('Procedures', function () { test( 'undoing and redoing deleting a procedure parameter maintains ' + 'the same state', - function () { + async function () { // Create a stack of container, parameter. const defBlock = createProcDefBlock(this.workspace); const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE); - mutatorIcon.setBubbleVisible(true); + await mutatorIcon.setBubbleVisible(true); const mutatorWorkspace = mutatorIcon.getWorkspace(); const containerBlock = mutatorWorkspace.getTopBlocks()[0]; const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg'); @@ -326,11 +326,11 @@ suite('Procedures', function () { }); suite('renaming procedure parameters', function () { - test('defs are updated for parameter renames', function () { + test('defs are updated for parameter renames', async function () { // Create a stack of container, parameter. const defBlock = createProcDefBlock(this.workspace); const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE); - mutatorIcon.setBubbleVisible(true); + await mutatorIcon.setBubbleVisible(true); const mutatorWorkspace = mutatorIcon.getWorkspace(); const containerBlock = mutatorWorkspace.getTopBlocks()[0]; const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg'); @@ -353,11 +353,11 @@ suite('Procedures', function () { ); }); - test('defs are updated for parameter renames when two params exist', function () { + test('defs are updated for parameter renames when two params exist', async function () { // Create a stack of container, parameter. const defBlock = createProcDefBlock(this.workspace); const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE); - mutatorIcon.setBubbleVisible(true); + await mutatorIcon.setBubbleVisible(true); const mutatorWorkspace = mutatorIcon.getWorkspace(); const containerBlock = mutatorWorkspace.getTopBlocks()[0]; const paramBlock1 = mutatorWorkspace.newBlock('procedures_mutatorarg'); @@ -383,12 +383,12 @@ suite('Procedures', function () { ); }); - test('callers are updated for parameter renames', function () { + test('callers are updated for parameter renames', async function () { // Create a stack of container, parameter. const defBlock = createProcDefBlock(this.workspace); const callBlock = createProcCallBlock(this.workspace); const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE); - mutatorIcon.setBubbleVisible(true); + await mutatorIcon.setBubbleVisible(true); const mutatorWorkspace = mutatorIcon.getWorkspace(); const containerBlock = mutatorWorkspace.getTopBlocks()[0]; const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg'); @@ -412,12 +412,12 @@ suite('Procedures', function () { ); }); - test('variables associated with procedure parameters are not renamed', function () { + test('variables associated with procedure parameters are not renamed', async function () { // Create a stack of container, parameter. const defBlock = createProcDefBlock(this.workspace); const callBlock = createProcCallBlock(this.workspace); const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE); - mutatorIcon.setBubbleVisible(true); + await mutatorIcon.setBubbleVisible(true); const mutatorWorkspace = mutatorIcon.getWorkspace(); const containerBlock = mutatorWorkspace.getTopBlocks()[0]; const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg'); @@ -436,11 +436,11 @@ suite('Procedures', function () { ); }); - test('renaming a variable associated with a parameter updates procedure defs', function () { + test('renaming a variable associated with a parameter updates procedure defs', async function () { // Create a stack of container, parameter. const defBlock = createProcDefBlock(this.workspace); const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE); - mutatorIcon.setBubbleVisible(true); + await mutatorIcon.setBubbleVisible(true); const mutatorWorkspace = mutatorIcon.getWorkspace(); const containerBlock = mutatorWorkspace.getTopBlocks()[0]; const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg'); @@ -464,11 +464,11 @@ suite('Procedures', function () { ); }); - test('renaming a variable associated with a parameter updates mutator parameters', function () { + test('renaming a variable associated with a parameter updates mutator parameters', async function () { // Create a stack of container, parameter. const defBlock = createProcDefBlock(this.workspace); const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE); - mutatorIcon.setBubbleVisible(true); + await mutatorIcon.setBubbleVisible(true); const mutatorWorkspace = mutatorIcon.getWorkspace(); const containerBlock = mutatorWorkspace.getTopBlocks()[0]; const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg'); @@ -488,12 +488,12 @@ suite('Procedures', function () { ); }); - test('renaming a variable associated with a parameter updates procedure callers', function () { + test('renaming a variable associated with a parameter updates procedure callers', async function () { // Create a stack of container, parameter. const defBlock = createProcDefBlock(this.workspace); const callBlock = createProcCallBlock(this.workspace); const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE); - mutatorIcon.setBubbleVisible(true); + await mutatorIcon.setBubbleVisible(true); const mutatorWorkspace = mutatorIcon.getWorkspace(); const containerBlock = mutatorWorkspace.getTopBlocks()[0]; const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg'); @@ -518,11 +518,11 @@ suite('Procedures', function () { ); }); - test('coalescing a variable associated with a parameter updates procedure defs', function () { + test('coalescing a variable associated with a parameter updates procedure defs', async function () { // Create a stack of container, parameter. const defBlock = createProcDefBlock(this.workspace); const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE); - mutatorIcon.setBubbleVisible(true); + await mutatorIcon.setBubbleVisible(true); const mutatorWorkspace = mutatorIcon.getWorkspace(); const containerBlock = mutatorWorkspace.getTopBlocks()[0]; const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg'); @@ -546,11 +546,11 @@ suite('Procedures', function () { ); }); - test('coalescing a variable associated with a parameter updates mutator parameters', function () { + test('coalescing a variable associated with a parameter updates mutator parameters', async function () { // Create a stack of container, parameter. const defBlock = createProcDefBlock(this.workspace); const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE); - mutatorIcon.setBubbleVisible(true); + await mutatorIcon.setBubbleVisible(true); const mutatorWorkspace = mutatorIcon.getWorkspace(); const containerBlock = mutatorWorkspace.getTopBlocks()[0]; const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg'); @@ -570,12 +570,12 @@ suite('Procedures', function () { ); }); - test('coalescing a variable associated with a parameter updates procedure callers', function () { + test('coalescing a variable associated with a parameter updates procedure callers', async function () { // Create a stack of container, parameter. const defBlock = createProcDefBlock(this.workspace); const callBlock = createProcCallBlock(this.workspace); const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE); - mutatorIcon.setBubbleVisible(true); + await mutatorIcon.setBubbleVisible(true); const mutatorWorkspace = mutatorIcon.getWorkspace(); const containerBlock = mutatorWorkspace.getTopBlocks()[0]; const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg'); @@ -606,11 +606,11 @@ suite('Procedures', function () { function () {}, ); - test('undoing renaming a procedure parameter reverts the change', function () { + test('undoing renaming a procedure parameter reverts the change', async function () { // Create a stack of container, parameter. const defBlock = createProcDefBlock(this.workspace); const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE); - mutatorIcon.setBubbleVisible(true); + await mutatorIcon.setBubbleVisible(true); const mutatorWorkspace = mutatorIcon.getWorkspace(); const containerBlock = mutatorWorkspace.getTopBlocks()[0]; const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg'); @@ -637,11 +637,11 @@ suite('Procedures', function () { ); }); - test('undoing and redoing renaming a procedure maintains the same state', function () { + test('undoing and redoing renaming a procedure maintains the same state', async function () { // Create a stack of container, parameter. const defBlock = createProcDefBlock(this.workspace); const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE); - mutatorIcon.setBubbleVisible(true); + await mutatorIcon.setBubbleVisible(true); const mutatorWorkspace = mutatorIcon.getWorkspace(); const containerBlock = mutatorWorkspace.getTopBlocks()[0]; const paramBlock = mutatorWorkspace.newBlock('procedures_mutatorarg'); @@ -670,11 +670,11 @@ suite('Procedures', function () { }); suite('reordering procedure parameters', function () { - test('reordering procedure parameters updates procedure blocks', function () { + test('reordering procedure parameters updates procedure blocks', async function () { // Create a stack of container, parameter, parameter. const defBlock = createProcDefBlock(this.workspace); const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE); - mutatorIcon.setBubbleVisible(true); + await mutatorIcon.setBubbleVisible(true); const mutatorWorkspace = mutatorIcon.getWorkspace(); const containerBlock = mutatorWorkspace.getTopBlocks()[0]; const paramBlock1 = mutatorWorkspace.newBlock('procedures_mutatorarg'); @@ -706,12 +706,12 @@ suite('Procedures', function () { ); }); - test('reordering procedure parameters updates caller blocks', function () { + test('reordering procedure parameters updates caller blocks', async function () { // Create a stack of container, parameter, parameter. const defBlock = createProcDefBlock(this.workspace); const callBlock = createProcCallBlock(this.workspace); const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE); - mutatorIcon.setBubbleVisible(true); + await mutatorIcon.setBubbleVisible(true); const mutatorWorkspace = mutatorIcon.getWorkspace(); const containerBlock = mutatorWorkspace.getTopBlocks()[0]; const paramBlock1 = mutatorWorkspace.newBlock('procedures_mutatorarg'); @@ -756,12 +756,12 @@ suite('Procedures', function () { test( 'reordering procedure parameters reorders the blocks ' + 'attached to caller inputs', - function () { + async function () { // Create a stack of container, parameter, parameter. const defBlock = createProcDefBlock(this.workspace); const callBlock = createProcCallBlock(this.workspace); const mutatorIcon = defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE); - mutatorIcon.setBubbleVisible(true); + await mutatorIcon.setBubbleVisible(true); const mutatorWorkspace = mutatorIcon.getWorkspace(); const containerBlock = mutatorWorkspace.getTopBlocks()[0]; const paramBlock1 = mutatorWorkspace.newBlock('procedures_mutatorarg'); @@ -1909,11 +1909,11 @@ suite('Procedures', function () { } }); suite('Untyped Arguments', function () { - function createMutator(argArray) { + async function createMutator(argArray) { const mutatorIcon = this.defBlock.getIcon( Blockly.icons.MutatorIcon.TYPE, ); - mutatorIcon.setBubbleVisible(true); + await mutatorIcon.setBubbleVisible(true); this.mutatorWorkspace = mutatorIcon.getWorkspace(); this.containerBlock = this.mutatorWorkspace.getTopBlocks()[0]; this.connection = @@ -1946,58 +1946,58 @@ suite('Procedures', function () { chai.assert.equal(this.callBlock.getVars()[i], argArray[i]); } } - test('Simple Add Arg', function () { + test('Simple Add Arg', async function () { const args = ['arg1']; - createMutator.call(this, args); + await createMutator.call(this, args); assertArgs.call(this, args); }); - test('Multiple Args', function () { + test('Multiple Args', async function () { const args = ['arg1', 'arg2', 'arg3']; - createMutator.call(this, args); + await createMutator.call(this, args); assertArgs.call(this, args); }); - test('Simple Change Arg', function () { - createMutator.call(this, ['arg1']); + test('Simple Change Arg', async function () { + await createMutator.call(this, ['arg1']); this.argBlock.setFieldValue('arg2', 'NAME'); this.defBlock.compose(this.containerBlock); assertArgs.call(this, ['arg2']); }); - test('lower -> CAPS', function () { - createMutator.call(this, ['arg']); + test('lower -> CAPS', async function () { + await createMutator.call(this, ['arg']); this.argBlock.setFieldValue('ARG', 'NAME'); this.defBlock.compose(this.containerBlock); assertArgs.call(this, ['ARG']); }); - test('CAPS -> lower', function () { - createMutator.call(this, ['ARG']); + test('CAPS -> lower', async function () { + await createMutator.call(this, ['ARG']); this.argBlock.setFieldValue('arg', 'NAME'); this.defBlock.compose(this.containerBlock); assertArgs.call(this, ['arg']); }); // Test case for #1958 - test('Set Arg Empty', function () { + test('Set Arg Empty', async function () { const args = ['arg1']; - createMutator.call(this, args); + await createMutator.call(this, args); this.argBlock.setFieldValue('', 'NAME'); this.defBlock.compose(this.containerBlock); assertArgs.call(this, args); }); - test('Whitespace', function () { + test('Whitespace', async function () { const args = ['arg1']; - createMutator.call(this, args); + await createMutator.call(this, args); this.argBlock.setFieldValue(' ', 'NAME'); this.defBlock.compose(this.containerBlock); assertArgs.call(this, args); }); - test('Whitespace and Text', function () { - createMutator.call(this, ['arg1']); + test('Whitespace and Text', async function () { + await createMutator.call(this, ['arg1']); this.argBlock.setFieldValue(' text ', 'NAME'); this.defBlock.compose(this.containerBlock); assertArgs.call(this, ['text']); }); - test('<>', function () { + test('<>', async function () { const args = ['<>']; - createMutator.call(this, args); + await createMutator.call(this, args); assertArgs.call(this, args); }); }); diff --git a/tests/mocha/comment_test.js b/tests/mocha/comment_test.js index 6f19aa7f0..452f07493 100644 --- a/tests/mocha/comment_test.js +++ b/tests/mocha/comment_test.js @@ -47,8 +47,8 @@ suite('Comments', function () { chai.assert.isNotOk(comment.textInputBubble); chai.assert.isOk(comment.textBubble); } - test('Editable', function () { - this.comment.setBubbleVisible(true); + test('Editable', async function () { + await this.comment.setBubbleVisible(true); chai.assert.isTrue(this.comment.bubbleIsVisible()); assertEditable(this.comment); assertEventFired( @@ -59,10 +59,10 @@ suite('Comments', function () { this.block.id, ); }); - test('Not Editable', function () { + test('Not Editable', async function () { sinon.stub(this.block, 'isEditable').returns(false); - this.comment.setBubbleVisible(true); + await this.comment.setBubbleVisible(true); chai.assert.isTrue(this.comment.bubbleIsVisible()); assertNotEditable(this.comment); @@ -74,11 +74,11 @@ suite('Comments', function () { this.block.id, ); }); - test('Editable -> Not Editable', function () { - this.comment.setBubbleVisible(true); + test('Editable -> Not Editable', async function () { + await this.comment.setBubbleVisible(true); sinon.stub(this.block, 'isEditable').returns(false); - this.comment.updateEditable(); + await this.comment.updateEditable(); chai.assert.isTrue(this.comment.bubbleIsVisible()); assertNotEditable(this.comment); @@ -90,14 +90,14 @@ suite('Comments', function () { this.block.id, ); }); - test('Not Editable -> Editable', function () { + test('Not Editable -> Editable', async function () { const editableStub = sinon.stub(this.block, 'isEditable').returns(false); - this.comment.setBubbleVisible(true); + await this.comment.setBubbleVisible(true); editableStub.returns(true); - this.comment.updateEditable(); + await this.comment.updateEditable(); chai.assert.isTrue(this.comment.bubbleIsVisible()); assertEditable(this.comment); assertEventFired( diff --git a/tests/mocha/mutator_test.js b/tests/mocha/mutator_test.js index 609dc03a2..b4c6930fa 100644 --- a/tests/mocha/mutator_test.js +++ b/tests/mocha/mutator_test.js @@ -31,10 +31,10 @@ suite('Mutator', function () { sharedTestTeardown.call(this); }); - test('No change', function () { + test('No change', async function () { const block = createRenderedBlock(this.workspace, 'xml_block'); const icon = block.getIcon(Blockly.icons.MutatorIcon.TYPE); - icon.setBubbleVisible(true); + await icon.setBubbleVisible(true); const mutatorWorkspace = icon.getWorkspace(); // Trigger mutator change listener. createRenderedBlock(mutatorWorkspace, 'checkbox_block'); @@ -43,10 +43,10 @@ suite('Mutator', function () { }); }); - test('XML', function () { + test('XML', async function () { const block = createRenderedBlock(this.workspace, 'xml_block'); const icon = block.getIcon(Blockly.icons.MutatorIcon.TYPE); - icon.setBubbleVisible(true); + await icon.setBubbleVisible(true); const mutatorWorkspace = icon.getWorkspace(); mutatorWorkspace .getBlockById('check_block') @@ -63,10 +63,10 @@ suite('Mutator', function () { ); }); - test('JSO', function () { + test('JSO', async function () { const block = createRenderedBlock(this.workspace, 'jso_block'); const icon = block.getIcon(Blockly.icons.MutatorIcon.TYPE); - icon.setBubbleVisible(true); + await icon.setBubbleVisible(true); const mutatorWorkspace = icon.getWorkspace(); mutatorWorkspace .getBlockById('check_block') From 2e1297e7654f2c36bb2c6312cafe5bc9d0f30ec0 Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Tue, 9 Jan 2024 13:58:49 -0800 Subject: [PATCH 08/92] fix: snap to grid immediately (#7745) --- core/block_svg.ts | 31 ++++++------------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/core/block_svg.ts b/core/block_svg.ts index c6c9c9fa3..f71205f4e 100644 --- a/core/block_svg.ts +++ b/core/block_svg.ts @@ -455,23 +455,12 @@ export class BlockSvg /** Snap this block to the nearest grid point. */ snapToGrid() { - if (this.isDeadOrDying()) { - return; // Deleted block. - } - if (this.workspace.isDragging()) { - return; // Don't bump blocks during a drag. - } - - if (this.getParent()) { - return; // Only snap top-level blocks. - } - if (this.isInFlyout) { - return; // Don't move blocks around in a flyout. - } + if (this.isDeadOrDying()) return; + if (this.getParent()) return; + if (this.isInFlyout) return; const grid = this.workspace.getGrid(); - if (!grid || !grid.shouldSnap()) { - return; // Config says no snapping. - } + if (!grid || !grid.shouldSnap()) return; + const spacing = grid.getSpacing(); const half = spacing / 2; const xy = this.getRelativeToSurfaceXY(); @@ -1530,15 +1519,7 @@ export class BlockSvg * @internal */ scheduleSnapAndBump() { - // Ensure that any snap and bump are part of this move's event group. - const group = eventUtils.getGroup(); - - setTimeout(() => { - eventUtils.setGroup(group); - this.snapToGrid(); - eventUtils.setGroup(false); - }, config.bumpDelay / 2); - + this.snapToGrid(); this.bumpNeighbours(); } From 75007a064c1849ddc0ffadc05f7475dc63f3dd8b Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Wed, 10 Jan 2024 10:31:34 -0800 Subject: [PATCH 09/92] chore!: delete deprecations for v11. (#7732) * chore: delete basic deprecations * chore: remove deprecated align enum * chore: remove generator deprecation * chore: format --- blocks/procedures.ts | 2 +- core/block_dragger.ts | 55 ------- core/block_svg.ts | 23 --- core/blockly.ts | 38 ----- core/clipboard.ts | 41 ------ core/generator.ts | 12 +- core/icons/icon.ts | 11 -- core/icons/mutator_icon.ts | 39 ----- core/inputs/input.ts | 23 +-- core/main.ts | 76 ---------- core/options.ts | 7 +- core/renderers/common/drawer.ts | 11 -- core/renderers/measurables/icon.ts | 7 - core/renderers/minimalist/constants.ts | 33 ----- core/renderers/minimalist/drawer.ts | 39 ----- core/renderers/minimalist/info.ts | 53 ------- core/renderers/minimalist/minimalist.ts | 16 --- core/renderers/minimalist/renderer.ts | 76 ---------- core/utils/dom.ts | 19 --- core/utils/string.ts | 21 --- core/workspace_svg.ts | 182 ------------------------ scripts/gulpfiles/build_tasks.js | 4 +- 22 files changed, 6 insertions(+), 782 deletions(-) delete mode 100644 core/renderers/minimalist/constants.ts delete mode 100644 core/renderers/minimalist/drawer.ts delete mode 100644 core/renderers/minimalist/info.ts delete mode 100644 core/renderers/minimalist/minimalist.ts delete mode 100644 core/renderers/minimalist/renderer.ts diff --git a/blocks/procedures.ts b/blocks/procedures.ts index 7150bda8a..7f4a0ef5c 100644 --- a/blocks/procedures.ts +++ b/blocks/procedures.ts @@ -14,7 +14,7 @@ import * as Xml from '../core/xml.js'; import * as fieldRegistry from '../core/field_registry.js'; import * as xmlUtils from '../core/utils/xml.js'; import type {Abstract as AbstractEvent} from '../core/events/events_abstract.js'; -import {Align} from '../core/inputs/input.js'; +import {Align} from '../core/inputs/align.js'; import type {Block} from '../core/block.js'; import type {BlockSvg} from '../core/block_svg.js'; import type {BlockCreate} from '../core/events/events_block_create.js'; diff --git a/core/block_dragger.ts b/core/block_dragger.ts index 78c1381ae..ef061b6d9 100644 --- a/core/block_dragger.ts +++ b/core/block_dragger.ts @@ -28,8 +28,6 @@ import * as registry from './registry.js'; import {Coordinate} from './utils/coordinate.js'; import * as dom from './utils/dom.js'; import type {WorkspaceSvg} from './workspace_svg.js'; -import {hasBubble} from './interfaces/i_has_bubble.js'; -import * as deprecation from './utils/deprecation.js'; import * as layers from './layers.js'; /** @@ -51,12 +49,6 @@ export class BlockDragger implements IBlockDragger { protected wouldDeleteBlock_ = false; protected startXY_: Coordinate; - /** - * @deprecated To be removed in v11. Updating icons is now handled by the - * block's `moveDuringDrag` method. - */ - protected dragIconData_: IconPositionData[] = []; - /** * @param block The block to drag. * @param workspace The workspace to drag on. @@ -76,8 +68,6 @@ export class BlockDragger implements IBlockDragger { * beginning of the drag in workspace coordinates. */ this.startXY_ = this.draggingBlock_.getRelativeToSurfaceXY(); - - this.dragIconData_ = initIconData(block, this.startXY_); } /** @@ -86,7 +76,6 @@ export class BlockDragger implements IBlockDragger { * @internal */ dispose() { - this.dragIconData_.length = 0; if (this.draggedConnectionManager_) { this.draggedConnectionManager_.dispose(); } @@ -398,16 +387,6 @@ export class BlockDragger implements IBlockDragger { return result; } - /** - * Move all of the icons connected to this drag. - * - * @deprecated To be removed in v11. This is now handled by the block's - * `moveDuringDrag` method. - */ - protected dragIcons_() { - deprecation.warn('Blockly.BlockDragger.prototype.dragIcons_', 'v10', 'v11'); - } - /** * Get a list of the insertion markers that currently exist. Drags have 0, 1, * or 2 insertion markers. @@ -432,38 +411,4 @@ export interface IconPositionData { icon: Icon; } -/** - * Make a list of all of the icons (comment, warning, and mutator) that are - * on this block and its descendants. Moving an icon moves the bubble that - * extends from it if that bubble is open. - * - * @param block The root block that is being dragged. - * @param blockOrigin The top left of the given block in workspace coordinates. - * @returns The list of all icons and their locations. - */ -function initIconData( - block: BlockSvg, - blockOrigin: Coordinate, -): IconPositionData[] { - // Build a list of icons that need to be moved and where they started. - const dragIconData = []; - - for (const icon of block.getIcons()) { - // Only bother to track icons whose bubble is visible. - if (hasBubble(icon) && !icon.bubbleIsVisible()) continue; - - dragIconData.push({location: blockOrigin, icon: icon}); - icon.onLocationChange(blockOrigin); - } - - for (const child of block.getChildren(false)) { - dragIconData.push( - ...initIconData(child, Coordinate.sum(blockOrigin, child.relativeCoords)), - ); - } - // AnyDuringMigration because: Type '{ location: Coordinate | null; icon: - // Icon; }[]' is not assignable to type 'IconPositionData[]'. - return dragIconData as AnyDuringMigration; -} - registry.register(registry.Type.BLOCK_DRAGGER, registry.DEFAULT, BlockDragger); diff --git a/core/block_svg.ts b/core/block_svg.ts index f71205f4e..f8ac57684 100644 --- a/core/block_svg.ts +++ b/core/block_svg.ts @@ -17,7 +17,6 @@ import './events/events_selected.js'; import {Block} from './block.js'; import * as blockAnimations from './block_animations.js'; import * as browserEvents from './browser_events.js'; -import {CommentIcon} from './icons/comment_icon.js'; import * as common from './common.js'; import {config} from './config.js'; import type {Connection} from './connection.js'; @@ -59,7 +58,6 @@ import {WarningIcon} from './icons/warning_icon.js'; import type {Workspace} from './workspace.js'; import type {WorkspaceSvg} from './workspace_svg.js'; import * as renderManagement from './render_management.js'; -import * as deprecation from './utils/deprecation.js'; import {IconType} from './icons/icon_types.js'; import {BlockCopyData, BlockPaster} from './clipboard/block_paster.js'; @@ -115,13 +113,6 @@ export class BlockSvg /** Block's mutator icon (if any). */ mutator: MutatorIcon | null = null; - /** - * Block's warning icon (if any). - * - * @deprecated Use `setWarningText` to modify warnings on this block. - */ - warning: WarningIcon | null = null; - private svgGroup_: SVGGElement; style: BlockStyle; /** @internal */ @@ -911,18 +902,6 @@ export class BlockSvg } } - /** - * Get the comment icon attached to this block, or null if the block has no - * comment. - * - * @returns The comment icon attached to this block, or null. - * @deprecated Use getIcon. To be remove in v11. - */ - getCommentIcon(): CommentIcon | null { - deprecation.warn('getCommentIcon', 'v10', 'v11', 'getIcon'); - return (this.getIcon(CommentIcon.TYPE) ?? null) as CommentIcon | null; - } - /** * Set this block's warning text. * @@ -1010,7 +989,6 @@ export class BlockSvg override addIcon(icon: T): T { super.addIcon(icon); - if (icon instanceof WarningIcon) this.warning = icon; if (icon instanceof MutatorIcon) this.mutator = icon; if (this.rendered) { @@ -1041,7 +1019,6 @@ export class BlockSvg override removeIcon(type: IconType): boolean { const removed = super.removeIcon(type); - if (type.equals(WarningIcon.TYPE)) this.warning = null; if (type.equals(MutatorIcon.TYPE)) this.mutator = null; if (this.rendered) { diff --git a/core/blockly.ts b/core/blockly.ts index 00c10f69e..96169be27 100644 --- a/core/blockly.ts +++ b/core/blockly.ts @@ -22,7 +22,6 @@ import {BlockSvg} from './block_svg.js'; import {BlocklyOptions} from './blockly_options.js'; import {Blocks} from './blocks.js'; import * as browserEvents from './browser_events.js'; -import {Bubble} from './bubbles/bubble.js'; import * as bubbles from './bubbles.js'; import {BubbleDragger} from './bubble_dragger.js'; import * as bumpObjects from './bump_objects.js'; @@ -123,9 +122,7 @@ import {Gesture} from './gesture.js'; import {Grid} from './grid.js'; import * as icons from './icons.js'; import {inject} from './inject.js'; -import {Align} from './inputs/align.js'; import {Input} from './inputs/input.js'; -import {inputTypes} from './inputs/input_types.js'; import * as inputs from './inputs.js'; import {InsertionMarkerManager} from './insertion_marker_manager.js'; import {IASTNodeLocation} from './interfaces/i_ast_node_location.js'; @@ -186,7 +183,6 @@ import * as renderManagement from './render_management.js'; import * as blockRendering from './renderers/common/block_rendering.js'; import * as constants from './constants.js'; import * as geras from './renderers/geras/geras.js'; -import * as minimalist from './renderers/minimalist/minimalist.js'; import * as thrasos from './renderers/thrasos/thrasos.js'; import * as zelos from './renderers/zelos/zelos.js'; import {Scrollbar} from './scrollbar.js'; @@ -241,27 +237,6 @@ export const VERSION = 'uncompiled'; * namespace to put new functions on. */ -/* - * Aliases for input alignments used in block defintions. - */ - -/** - * @see Blockly.Input.Align.LEFT - * @deprecated Use `Blockly.inputs.Align.LEFT`. To be removed in v11. - */ -export const ALIGN_LEFT = Align.LEFT; - -/** - * @see Blockly.Input.Align.CENTRE - * @deprecated Use `Blockly.inputs.Align.CENTER`. To be removed in v11. - */ -export const ALIGN_CENTRE = Align.CENTRE; - -/** - * @see Blockly.Input.Align.RIGHT - * @deprecated Use `Blockly.inputs.Align.RIGHT`. To be removed in v11. - */ -export const ALIGN_RIGHT = Align.RIGHT; /* * Aliases for constants used for connection and input types. */ @@ -286,12 +261,6 @@ export const NEXT_STATEMENT = ConnectionType.NEXT_STATEMENT; */ export const PREVIOUS_STATEMENT = ConnectionType.PREVIOUS_STATEMENT; -/** - * @see inputTypes.DUMMY_INPUT - * @deprecated Use `Blockly.inputs.inputTypes.DUMMY`. To be removed in v11. - */ -export const DUMMY_INPUT = inputTypes.DUMMY; - /** Aliases for toolbox positions. */ /** @@ -488,7 +457,6 @@ export {constants}; export {dialog}; export {fieldRegistry}; export {geras}; -export {minimalist}; export {registry}; export {thrasos}; export {uiPosition}; @@ -502,8 +470,6 @@ export {BlockDragger}; export {BlockSvg}; export {Blocks}; export {bubbles}; -/** @deprecated Use Blockly.bubbles.Bubble instead. To be removed in v11. */ -export {Bubble}; export {BubbleDragger}; export {CollapsibleToolboxCategory}; export {ComponentManager}; @@ -648,9 +614,5 @@ export {WorkspaceDragger}; export {WorkspaceSvg}; export {ZoomControls}; export {config}; -/** @deprecated Use Blockly.ConnectionType instead. */ -export const connectionTypes = ConnectionType; export {inject}; -/** @deprecated Use Blockly.inputs.inputTypes instead. To be removed in v11. */ -export {inputTypes}; export {serialization}; diff --git a/core/clipboard.ts b/core/clipboard.ts index 2c3872c1a..ed574d112 100644 --- a/core/clipboard.ts +++ b/core/clipboard.ts @@ -12,30 +12,12 @@ import * as globalRegistry from './registry.js'; import {WorkspaceSvg} from './workspace_svg.js'; import * as registry from './clipboard/registry.js'; import {Coordinate} from './utils/coordinate.js'; -import * as deprecation from './utils/deprecation.js'; /** Metadata about the object that is currently on the clipboard. */ let stashedCopyData: ICopyData | null = null; let stashedWorkspace: WorkspaceSvg | null = null; -/** - * Copy a copyable element onto the local clipboard. - * - * @param toCopy The copyable element to be copied. - * @deprecated v11. Use `myCopyable.toCopyData()` instead. To be removed v12. - * @internal - */ -export function copy(toCopy: ICopyable): T | null { - deprecation.warn( - 'Blockly.clipboard.copy', - 'v11', - 'v12', - 'myCopyable.toCopyData()', - ); - return TEST_ONLY.copyInternal(toCopy); -} - /** * Private version of copy for stubbing in tests. */ @@ -107,29 +89,6 @@ function pasteFromData( ?.paste(copyData, workspace, coordinate) ?? null) as ICopyable | null; } -/** - * Duplicate this copy-paste-able element. - * - * @param toDuplicate The element to be duplicated. - * @returns The element that was duplicated, or null if the duplication failed. - * @deprecated v11. Use - * `Blockly.clipboard.paste(myCopyable.toCopyData(), myWorkspace)` instead. - * To be removed v12. - * @internal - */ -export function duplicate< - U extends ICopyData, - T extends ICopyable & IHasWorkspace, ->(toDuplicate: T): T | null { - deprecation.warn( - 'Blockly.clipboard.duplicate', - 'v11', - 'v12', - 'Blockly.clipboard.paste(myCopyable.toCopyData(), myWorkspace)', - ); - return TEST_ONLY.duplicateInternal(toDuplicate); -} - /** * Private version of duplicate for stubbing in tests. */ diff --git a/core/generator.ts b/core/generator.ts index d94597fc1..35a962396 100644 --- a/core/generator.ts +++ b/core/generator.ts @@ -16,7 +16,6 @@ import type {Block} from './block.js'; import * as common from './common.js'; import {Names, NameType} from './names.js'; import type {Workspace} from './workspace.js'; -import {warn} from './utils/deprecation.js'; /** * Deprecated, no-longer used type declaration for per-block-type generator @@ -255,16 +254,7 @@ export class CodeGenerator { // Look up block generator function in dictionary - but fall back // to looking up on this if not found, for backwards compatibility. - let func = this.forBlock[block.type]; - if (!func && (this as any)[block.type]) { - warn( - 'block generator functions on CodeGenerator objects', - '10.0', - '11.0', - 'the .forBlock[blockType] dictionary', - ); - func = (this as any)[block.type]; - } + const func = this.forBlock[block.type]; if (typeof func !== 'function') { throw Error( `${this.name_} generator does not know how to generate code ` + diff --git a/core/icons/icon.ts b/core/icons/icon.ts index b1104b157..6ad953236 100644 --- a/core/icons/icon.ts +++ b/core/icons/icon.ts @@ -14,7 +14,6 @@ import * as dom from '../utils/dom.js'; import {Size} from '../utils/size.js'; import {Svg} from '../utils/svg.js'; import type {IconType} from './icon_types.js'; -import * as deprecation from '../utils/deprecation.js'; import * as tooltip from '../tooltip.js'; /** @@ -145,14 +144,4 @@ export abstract class Icon implements IIcon { isClickableInFlyout(autoClosingFlyout: boolean): boolean { return true; } - - /** - * Sets the visibility of the icon's bubble if one exists. - * - * @deprecated Use `setBubbleVisible` instead. To be removed in v11. - */ - setVisible(visibility: boolean): void { - deprecation.warn('setVisible', 'v10', 'v11', 'setBubbleVisible'); - if (hasBubble(this)) this.setBubbleVisible(visibility); - } } diff --git a/core/icons/mutator_icon.ts b/core/icons/mutator_icon.ts index f4c1ea83d..7fb3fcf3b 100644 --- a/core/icons/mutator_icon.ts +++ b/core/icons/mutator_icon.ts @@ -7,11 +7,9 @@ // Former goog.module ID: Blockly.Mutator import type {Abstract} from '../events/events_abstract.js'; -import type {Block} from '../block.js'; import {BlockChange} from '../events/events_block_change.js'; import type {BlocklyOptions} from '../blockly_options.js'; import type {BlockSvg} from '../block_svg.js'; -import type {Connection} from '../connection.js'; import {Coordinate} from '../utils/coordinate.js'; import * as dom from '../utils/dom.js'; import * as eventUtils from '../events/utils.js'; @@ -22,7 +20,6 @@ import {Rect} from '../utils/rect.js'; import {Size} from '../utils/size.js'; import {Svg} from '../utils/svg.js'; import type {WorkspaceSvg} from '../workspace_svg.js'; -import * as deprecation from '../utils/deprecation.js'; import {IconType} from './icon_types.js'; import * as renderManagement from '../render_management.js'; @@ -354,40 +351,4 @@ export class MutatorIcon extends Icon implements IHasBubble { getWorkspace(): WorkspaceSvg | undefined { return this.miniWorkspaceBubble?.getWorkspace(); } - - /** - * Reconnects the given connection to the mutated input on the given block. - * - * @deprecated Use connection.reconnect instead. To be removed in v11. - */ - static reconnect( - connectionChild: Connection | null, - block: Block, - inputName: string, - ): boolean { - deprecation.warn( - 'MutatorIcon.reconnect', - 'v10', - 'v11', - 'connection.reconnect', - ); - if (!connectionChild) return false; - return connectionChild.reconnect(block, inputName); - } - - /** - * Returns the parent workspace of a workspace that is inside a mini workspace - * bubble, taking into account whether the workspace is a flyout. - * - * @deprecated Use workspace.getRootWorkspace. To be removed in v11. - */ - static findParentWs(workspace: WorkspaceSvg): WorkspaceSvg | null { - deprecation.warn( - 'MutatorIcon.findParentWs', - 'v10', - 'v11', - 'workspace.getRootWorkspace', - ); - return workspace.getRootWorkspace(); - } } diff --git a/core/inputs/input.ts b/core/inputs/input.ts index 767c07965..96498ec1b 100644 --- a/core/inputs/input.ts +++ b/core/inputs/input.ts @@ -22,6 +22,7 @@ import type {Field} from '../field.js'; import * as fieldRegistry from '../field_registry.js'; import type {RenderedConnection} from '../rendered_connection.js'; import {inputTypes} from './input_types.js'; +import {Align} from './align.js'; /** Class for an input with optional fields. */ export class Input { @@ -305,25 +306,3 @@ export class Input { return this.sourceBlock.makeConnection_(type); } } - -export namespace Input { - // TODO(v11): When this is removed in v11, also re-enable errors on access - // of deprecated things (in build_tasks.js). - /** - * Enum for alignment of inputs. - * - * @deprecated Use Blockly.inputs.Align. To be removed in v11. - */ - export enum Align { - LEFT = -1, - CENTRE = 0, - RIGHT = 1, - } -} - -/** @deprecated Use Blockly.inputs.Align. To be removed in v11. */ -/** @suppress {deprecated} */ -export type Align = Input.Align; -/** @deprecated Use Blockly.inputs.Align. To be removed in v11. */ -/** @suppress {deprecated} */ -export const Align = Input.Align; diff --git a/core/main.ts b/core/main.ts index f794a8ab3..c301e9890 100644 --- a/core/main.ts +++ b/core/main.ts @@ -12,83 +12,7 @@ // Former goog.module ID: Blockly.main -import * as Blockly from './blockly.js'; import * as Msg from './msg.js'; -import * as colour from './utils/colour.js'; -import * as deprecation from './utils/deprecation.js'; - -/* - * Aliased functions and properties that used to be on the Blockly namespace. - * Everything in this section is deprecated. Both external and internal code - * should avoid using these functions and use the designated replacements. - * Everything in this section will be removed in a future version of Blockly. - */ - -// Add accessors for properties on Blockly that have now been deprecated. -// This will not work in uncompressed mode; it depends on Blockly being -// transpiled from a ES Module object to a plain object by Closure Compiler. -Object.defineProperties(Blockly, { - /** - * The richness of block colours, regardless of the hue. - * Must be in the range of 0 (inclusive) to 1 (exclusive). - * - * @name Blockly.HSV_SATURATION - * @type {number} - * @deprecated Use Blockly.utils.colour.getHsvSaturation() / - * .setHsvSaturation() instead. (July 2023) - * @suppress {checkTypes} - */ - HSV_SATURATION: { - get: function () { - deprecation.warn( - 'Blockly.HSV_SATURATION', - 'version 10', - 'version 11', - 'Blockly.utils.colour.getHsvSaturation()', - ); - return colour.getHsvSaturation(); - }, - set: function (newValue) { - deprecation.warn( - 'Blockly.HSV_SATURATION', - 'version 10', - 'version 11', - 'Blockly.utils.colour.setHsvSaturation()', - ); - colour.setHsvSaturation(newValue); - }, - }, - /** - * The intensity of block colours, regardless of the hue. - * Must be in the range of 0 (inclusive) to 1 (exclusive). - * - * @name Blockly.HSV_VALUE - * @type {number} - * @deprecated Use Blockly.utils.colour.getHsvValue() / .setHsvValue instead. - * (July 2023) - * @suppress {checkTypes} - */ - HSV_VALUE: { - get: function () { - deprecation.warn( - 'Blockly.HSV_VALUE', - 'version 10', - 'version 11', - 'Blockly.utils.colour.getHsvValue()', - ); - return colour.getHsvValue(); - }, - set: function (newValue) { - deprecation.warn( - 'Blockly.HSV_VALUE', - 'version 10', - 'version 11', - 'Blockly.utils.colour.setHsvValue()', - ); - colour.setHsvValue(newValue); - }, - }, -}); // If Blockly is compiled with ADVANCED_COMPILATION and/or loaded as a // CJS or ES module there will not be a Blockly global variable diff --git a/core/options.ts b/core/options.ts index 3b6483a26..42d2b41de 100644 --- a/core/options.ts +++ b/core/options.ts @@ -12,7 +12,6 @@ // Former goog.module ID: Blockly.Options import type {BlocklyOptions} from './blockly_options.js'; -import * as deprecation from './utils/deprecation.js'; import * as registry from './registry.js'; import {Theme} from './theme.js'; import {Classic} from './theme/classic.js'; @@ -38,6 +37,7 @@ export class Options { pathToMedia: string; hasCategories: boolean; moveOptions: MoveOptions; + /** @deprecated January 2019 */ hasScrollbars: boolean; hasTrashcan: boolean; maxTrashcanContents: number; @@ -143,10 +143,6 @@ export class Options { pathToMedia = options['media'].endsWith('/') ? options['media'] : options['media'] + '/'; - } else if ('path' in options) { - // 'path' is a deprecated option which has been replaced by 'media'. - deprecation.warn('path', 'Nov 2014', 'Jul 2023', 'media'); - pathToMedia = (options as any)['path'] + 'media/'; } const rawOneBasedIndex = options['oneBasedIndex']; const oneBasedIndex = @@ -172,7 +168,6 @@ export class Options { this.pathToMedia = pathToMedia; this.hasCategories = hasCategories; this.moveOptions = Options.parseMoveOptions_(options, hasCategories); - /** @deprecated January 2019 */ this.hasScrollbars = !!this.moveOptions.scrollbars; this.hasTrashcan = hasTrashcan; this.maxTrashcanContents = maxTrashcanContents; diff --git a/core/renderers/common/drawer.ts b/core/renderers/common/drawer.ts index e361a606d..e307b88bc 100644 --- a/core/renderers/common/drawer.ts +++ b/core/renderers/common/drawer.ts @@ -21,7 +21,6 @@ import {Types} from '../measurables/types.js'; import {isDynamicShape} from './constants.js'; import type {ConstantProvider, Notch, PuzzleTab} from './constants.js'; import type {RenderInfo} from './info.js'; -import * as deprecation from '../../utils/deprecation.js'; /** * An object that draws a block based on the given rendering information. @@ -68,16 +67,6 @@ export class Drawer { this.recordSizeOnBlock_(); } - /** - * Hide icons that were marked as hidden. - * - * @deprecated Manually hiding icons is no longer necessary. To be removed - * in v11. - */ - protected hideHiddenIcons_() { - deprecation.warn('hideHiddenIcons_', 'v10', 'v11'); - } - /** * Save sizing information back to the block * Most of the rendering information can be thrown away at the end of the diff --git a/core/renderers/measurables/icon.ts b/core/renderers/measurables/icon.ts index d681dbbd9..98e3f722d 100644 --- a/core/renderers/measurables/icon.ts +++ b/core/renderers/measurables/icon.ts @@ -12,18 +12,12 @@ import type {ConstantProvider} from '../common/constants.js'; import {Measurable} from './base.js'; import {Types} from './types.js'; -import {hasBubble} from '../../interfaces/i_has_bubble.js'; /** * An object containing information about the space an icon takes up during * rendering. */ export class Icon extends Measurable { - /** - * @deprecated Will be removed in v11. Create a subclass of the Icon - * measurable if this data is necessary for you. - */ - isVisible: boolean; flipRtl = false; /** @@ -39,7 +33,6 @@ export class Icon extends Measurable { ) { super(constants); - this.isVisible = hasBubble(icon) && icon.bubbleIsVisible(); this.type |= Types.ICON; const size = icon.getSize(); diff --git a/core/renderers/minimalist/constants.ts b/core/renderers/minimalist/constants.ts deleted file mode 100644 index 83bfaff0a..000000000 --- a/core/renderers/minimalist/constants.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -// Former goog.module ID: Blockly.minimalist.ConstantProvider - -import {ConstantProvider as BaseConstantProvider} from '../common/constants.js'; -import * as deprecation from '../../utils/deprecation.js'; - -/** - * An object that provides constants for rendering blocks in the minimalist - * renderer. - * - * @deprecated Use Blockly.blockRendering.ConstantProvider instead. - * To be removed in v11. - */ -export class ConstantProvider extends BaseConstantProvider { - /** - * @deprecated Use Blockly.blockRendering.ConstantProvider instead. - * To be removed in v11. - */ - constructor() { - super(); - deprecation.warn( - 'Blockly.minimalist.ConstantProvider', - 'v10', - 'v11', - 'Blockly.blockRendering.ConstantProvider', - ); - } -} diff --git a/core/renderers/minimalist/drawer.ts b/core/renderers/minimalist/drawer.ts deleted file mode 100644 index d0985674c..000000000 --- a/core/renderers/minimalist/drawer.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -// Former goog.module ID: Blockly.minimalist.Drawer - -import type {BlockSvg} from '../../block_svg.js'; -import {Drawer as BaseDrawer} from '../common/drawer.js'; -import * as deprecation from '../../utils/deprecation.js'; - -import type {RenderInfo} from './info.js'; - -/** - * An object that draws a block based on the given rendering information. - * - * @deprecated Use Blockly.blockRendering.Drawer instead. - * To be removed in v11. - */ -export class Drawer extends BaseDrawer { - /** - * @param block The block to render. - * @param info An object containing all information needed to render this - * block. - * - * @deprecated Use Blockly.blockRendering.Drawer instead. - * To be removed in v11. - */ - constructor(block: BlockSvg, info: RenderInfo) { - super(block, info); - deprecation.warn( - 'Blockly.minimalist.Drawer', - 'v10', - 'v11', - 'Blockly.blockRendering.Drawer', - ); - } -} diff --git a/core/renderers/minimalist/info.ts b/core/renderers/minimalist/info.ts deleted file mode 100644 index ae86e3eb6..000000000 --- a/core/renderers/minimalist/info.ts +++ /dev/null @@ -1,53 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -// Former goog.module ID: Blockly.minimalist.RenderInfo - -import type {BlockSvg} from '../../block_svg.js'; -import {RenderInfo as BaseRenderInfo} from '../common/info.js'; -import * as deprecation from '../../utils/deprecation.js'; - -import type {Renderer} from './renderer.js'; - -/** - * An object containing all sizing information needed to draw this block. - * - * This measure pass does not propagate changes to the block (although fields - * may choose to rerender when getSize() is called). However, calling it - * repeatedly may be expensive. - * - * @deprecated Use Blockly.blockRendering.RenderInfo instead. To be removed - * in v11. - */ -export class RenderInfo extends BaseRenderInfo { - // Exclamation is fine b/c this is assigned by the super constructor. - protected override renderer_!: Renderer; - - /** - * @param renderer The renderer in use. - * @param block The block to measure. - * @deprecated Use Blockly.blockRendering.RenderInfo instead. To be removed - * in v11. - */ - constructor(renderer: Renderer, block: BlockSvg) { - super(renderer, block); - deprecation.warn( - 'Blockly.minimalist.RenderInfo', - 'v10', - 'v11', - 'Blockly.blockRendering.RenderInfo', - ); - } - - /** - * Get the block renderer in use. - * - * @returns The block renderer in use. - */ - override getRenderer(): Renderer { - return this.renderer_; - } -} diff --git a/core/renderers/minimalist/minimalist.ts b/core/renderers/minimalist/minimalist.ts deleted file mode 100644 index 4ec0a220c..000000000 --- a/core/renderers/minimalist/minimalist.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** @file Re-exports of Blockly.minimalist.* modules. */ - -/** - * @license - * Copyright 2021 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -// Former goog.module ID: Blockly.minimalist - -import {ConstantProvider} from './constants.js'; -import {Drawer} from './drawer.js'; -import {RenderInfo} from './info.js'; -import {Renderer} from './renderer.js'; - -export {ConstantProvider, Drawer, Renderer, RenderInfo}; diff --git a/core/renderers/minimalist/renderer.ts b/core/renderers/minimalist/renderer.ts deleted file mode 100644 index b15b48f19..000000000 --- a/core/renderers/minimalist/renderer.ts +++ /dev/null @@ -1,76 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -// Former goog.module ID: Blockly.minimalist.Renderer - -import type {BlockSvg} from '../../block_svg.js'; -import * as blockRendering from '../common/block_rendering.js'; -import type {RenderInfo as BaseRenderInfo} from '../common/info.js'; -import {Renderer as BaseRenderer} from '../common/renderer.js'; -import * as deprecation from '../../utils/deprecation.js'; - -import {ConstantProvider} from './constants.js'; -import {Drawer} from './drawer.js'; -import {RenderInfo} from './info.js'; - -/** - * The minimalist renderer. - * - * @deprecated Use Blockly.blockRendering.Renderer instead. To be removed - * in v11. - */ -export class Renderer extends BaseRenderer { - /** - * @param name The renderer name. - * @deprecated Use Blockly.blockRendering.Renderer instead. To be removed - * in v11. - */ - constructor(name: string) { - super(name); - deprecation.warn( - 'Blockly.minimalist.Renderer', - 'v10', - 'v11', - 'Blockly.blockRendering.Renderer', - ); - } - - /** - * Create a new instance of the renderer's constant provider. - * - * @returns The constant provider. - */ - protected override makeConstants_(): ConstantProvider { - return new ConstantProvider(); - } - - /** - * Create a new instance of the renderer's render info object. - * - * @param block The block to measure. - * @returns The render info object. - */ - protected override makeRenderInfo_(block: BlockSvg): RenderInfo { - return new RenderInfo(this, block); - } - - /** - * Create a new instance of the renderer's drawer. - * - * @param block The block to render. - * @param info An object containing all information needed to render this - * block. - * @returns The drawer. - */ - protected override makeDrawer_( - block: BlockSvg, - info: BaseRenderInfo, - ): Drawer { - return new Drawer(block, info as RenderInfo); - } -} - -blockRendering.register('minimalist', Renderer); diff --git a/core/utils/dom.ts b/core/utils/dom.ts index e9efca9f8..e318e7e91 100644 --- a/core/utils/dom.ts +++ b/core/utils/dom.ts @@ -6,7 +6,6 @@ // Former goog.module ID: Blockly.utils.dom -import * as deprecation from './deprecation.js'; import type {Svg} from './svg.js'; /** @@ -154,24 +153,6 @@ export function insertAfter(newNode: Element, refNode: Element) { } } -/** - * Whether a node contains another node. - * - * @param parent The node that should contain the other node. - * @param descendant The node to test presence of. - * @returns Whether the parent node contains the descendant node. - * @deprecated Use native 'contains' DOM method. - */ -export function containsNode(parent: Node, descendant: Node): boolean { - deprecation.warn( - 'Blockly.utils.dom.containsNode', - 'version 10', - 'version 11', - 'Use native "contains" DOM method', - ); - return parent.contains(descendant); -} - /** * Sets the CSS transform property on an element. This function sets the * non-vendor-prefixed and vendor-prefixed versions for backwards compatibility diff --git a/core/utils/string.ts b/core/utils/string.ts index 046415bda..fbe9f5210 100644 --- a/core/utils/string.ts +++ b/core/utils/string.ts @@ -6,27 +6,6 @@ // Former goog.module ID: Blockly.utils.string -import * as deprecation from './deprecation.js'; - -/** - * Fast prefix-checker. - * Copied from Closure's goog.string.startsWith. - * - * @param str The string to check. - * @param prefix A string to look for at the start of `str`. - * @returns True if `str` begins with `prefix`. - * @deprecated Use built-in **string.startsWith** instead. - */ -export function startsWith(str: string, prefix: string): boolean { - deprecation.warn( - 'Blockly.utils.string.startsWith()', - 'April 2022', - 'April 2023', - 'Use built-in string.startsWith', - ); - return str.startsWith(prefix); -} - /** * Given an array of strings, return the length of the shortest one. * diff --git a/core/workspace_svg.ts b/core/workspace_svg.ts index fe1386587..68912f3f0 100644 --- a/core/workspace_svg.ts +++ b/core/workspace_svg.ts @@ -24,7 +24,6 @@ import type {BlocklyOptions} from './blockly_options.js'; import * as browserEvents from './browser_events.js'; import * as common from './common.js'; import {ComponentManager} from './component_manager.js'; -import {config} from './config.js'; import {ConnectionDB} from './connection_db.js'; import * as ContextMenu from './contextmenu.js'; import {ContextMenuRegistry} from './contextmenu_registry.js'; @@ -35,7 +34,6 @@ import {Gesture} from './gesture.js'; import {Grid} from './grid.js'; import type {IASTNodeLocationSvg} from './interfaces/i_ast_node_location_svg.js'; import type {IBoundedElement} from './interfaces/i_bounded_element.js'; -import type {ICopyData, ICopyable} from './interfaces/i_copyable.js'; import type {IDragTarget} from './interfaces/i_drag_target.js'; import type {IFlyout} from './interfaces/i_flyout.js'; import type {IMetricsManager} from './interfaces/i_metrics_manager.js'; @@ -49,7 +47,6 @@ import * as registry from './registry.js'; import * as blockRendering from './renderers/common/block_rendering.js'; import type {Renderer} from './renderers/common/renderer.js'; import type {ScrollbarPair} from './scrollbar_pair.js'; -import * as blocks from './serialization/blocks.js'; import type {Theme} from './theme.js'; import {Classic} from './theme/classic.js'; import {ThemeManager} from './theme_manager.js'; @@ -73,11 +70,9 @@ import {Workspace} from './workspace.js'; import {WorkspaceAudio} from './workspace_audio.js'; import {WorkspaceComment} from './workspace_comment.js'; import {WorkspaceCommentSvg} from './workspace_comment_svg.js'; -import * as Xml from './xml.js'; import {ZoomControls} from './zoom_controls.js'; import {ContextMenuOption} from './contextmenu_registry.js'; import * as renderManagement from './render_management.js'; -import * as deprecation from './utils/deprecation.js'; import {LayerManager} from './layer_manager.js'; /** Margin around the top/bottom/left/right after a zoomToFit call. */ @@ -1313,183 +1308,6 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { } } - /** - * Pastes the provided block or workspace comment onto the workspace. - * Does not check whether there is remaining capacity for the object, that - * should be done before calling this method. - * - * @param state The representation of the thing to paste. - * @returns The pasted thing, or null if the paste was not successful. - * @deprecated v10. Use `Blockly.clipboard.paste` instead. To be removed in - * v11. - */ - paste( - state: AnyDuringMigration | Element | DocumentFragment, - ): ICopyable | null { - deprecation.warn( - 'Blockly.WorkspaceSvg.prototype.paste', - 'v10', - 'v11', - 'Blockly.clipboard.paste', - ); - if (!this.rendered || (!state['type'] && !state['tagName'])) { - return null; - } - if (this.currentGesture_) { - // Dragging while pasting? No. - this.currentGesture_.cancel(); - } - - const existingGroup = eventUtils.getGroup(); - if (!existingGroup) { - eventUtils.setGroup(true); - } - let pastedThing; - try { - // Checks if this is JSON. JSON has a type property, while elements don't. - if (state['type']) { - pastedThing = this.pasteBlock_(null, state as blocks.State); - } else { - const xmlBlock = state as Element; - if (xmlBlock.tagName.toLowerCase() === 'comment') { - pastedThing = this.pasteWorkspaceComment_(xmlBlock); - } else { - pastedThing = this.pasteBlock_(xmlBlock, null); - } - } - } finally { - eventUtils.setGroup(existingGroup); - } - return pastedThing; - } - - /** - * Paste the provided block onto the workspace. - * - * @param xmlBlock XML block element. - * @param jsonBlock JSON block representation. - * @returns The pasted block. - */ - private pasteBlock_( - xmlBlock: Element | null, - jsonBlock: blocks.State | null, - ): BlockSvg { - eventUtils.disable(); - let block: BlockSvg; - try { - let blockX = 0; - let blockY = 0; - if (xmlBlock) { - block = Xml.domToBlockInternal(xmlBlock, this) as BlockSvg; - blockX = parseInt(xmlBlock.getAttribute('x') ?? '0'); - if (this.RTL) { - blockX = -blockX; - } - blockY = parseInt(xmlBlock.getAttribute('y') ?? '0'); - } else if (jsonBlock) { - block = blocks.append(jsonBlock, this) as BlockSvg; - blockX = jsonBlock['x'] || 10; - if (this.RTL) { - blockX = this.getWidth() - blockX; - } - blockY = jsonBlock['y'] || 10; - } - - // Move the duplicate to original position. - if (!isNaN(blockX) && !isNaN(blockY)) { - // Offset block until not clobbering another block and not in connection - // distance with neighbouring blocks. - let collide; - do { - collide = false; - const allBlocks = this.getAllBlocks(false); - for (let i = 0, otherBlock; (otherBlock = allBlocks[i]); i++) { - const otherXY = otherBlock.getRelativeToSurfaceXY(); - if ( - Math.abs(blockX - otherXY.x) <= 1 && - Math.abs(blockY - otherXY.y) <= 1 - ) { - collide = true; - break; - } - } - if (!collide) { - // Check for blocks in snap range to any of its connections. - const connections = block!.getConnections_(false); - for (let i = 0, connection; (connection = connections[i]); i++) { - const neighbour = connection.closest( - config.snapRadius, - // This code doesn't work because it's passing absolute coords - // instead of relative coords. But we're deprecating the `paste` - // function anyway so we're not going to fix it. - new Coordinate(blockX, blockY), - ); - if (neighbour.connection) { - collide = true; - break; - } - } - } - if (collide) { - if (this.RTL) { - blockX -= config.snapRadius; - } else { - blockX += config.snapRadius; - } - blockY += config.snapRadius * 2; - } - } while (collide); - // No 'reason' provided since events are disabled. - block!.moveTo(new Coordinate(blockX, blockY)); - } - } finally { - eventUtils.enable(); - } - if (eventUtils.isEnabled() && !block!.isShadow()) { - eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CREATE))(block!)); - } - block!.select(); - return block!; - } - - /** - * Paste the provided comment onto the workspace. - * - * @param xmlComment XML workspace comment element. - * @returns The pasted workspace comment. - */ - private pasteWorkspaceComment_(xmlComment: Element): WorkspaceCommentSvg { - eventUtils.disable(); - let comment: WorkspaceCommentSvg; - try { - comment = WorkspaceCommentSvg.fromXmlRendered(xmlComment, this); - // Move the duplicate to original position. - let commentX = parseInt(xmlComment.getAttribute('x') ?? '0'); - let commentY = parseInt(xmlComment.getAttribute('y') ?? '0'); - if (!isNaN(commentX) && !isNaN(commentY)) { - if (this.RTL) { - commentX = -commentX; - } - // Offset workspace comment. - // TODO (#1719): Properly offset comment such that it's not interfering - // with any blocks. - commentX += 50; - commentY += 50; - // This code doesn't work because it's passing absolute coords - // instead of relative coords. But we're deprecating the `paste` - // function anyway so we're not going to fix it. - comment.moveBy(commentX, commentY); - } - } finally { - eventUtils.enable(); - } - if (eventUtils.isEnabled()) { - WorkspaceComment.fireCreateEvent(comment); - } - comment.select(); - return comment; - } - /** * Refresh the toolbox unless there's a drag in progress. * diff --git a/scripts/gulpfiles/build_tasks.js b/scripts/gulpfiles/build_tasks.js index 8775f6918..ca7f11073 100644 --- a/scripts/gulpfiles/build_tasks.js +++ b/scripts/gulpfiles/build_tasks.js @@ -208,6 +208,8 @@ const JSCOMP_ERROR = [ 'conformanceViolations', 'const', 'constantProperty', + 'deprecated', + 'deprecatedAnnotations', 'duplicateMessage', 'es5Strict', 'externsValidation', @@ -254,8 +256,6 @@ const JSCOMP_ERROR = [ * it's generally sufficient to remove them from JSCOMP_ERROR. */ const JSCOMP_WARNING = [ - 'deprecated', - 'deprecatedAnnotations', ]; /** From f317068aeba052fee4b38899584448bdd88ad72a Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Wed, 10 Jan 2024 10:35:50 -0800 Subject: [PATCH 10/92] fix: have insertion markers use json serialization (#7730) --- core/insertion_marker_manager.ts | 65 ++++++++------------------------ 1 file changed, 16 insertions(+), 49 deletions(-) diff --git a/core/insertion_marker_manager.ts b/core/insertion_marker_manager.ts index 69d3d2cb9..cc507bd70 100644 --- a/core/insertion_marker_manager.ts +++ b/core/insertion_marker_manager.ts @@ -17,7 +17,7 @@ import type {BlockSvg} from './block_svg.js'; import * as common from './common.js'; import {ComponentManager} from './component_manager.js'; import {config} from './config.js'; -import * as constants from './constants.js'; +import * as blocks from './serialization/blocks.js'; import * as eventUtils from './events/utils.js'; import type {IDeleteArea} from './interfaces/i_delete_area.js'; import type {IDragTarget} from './interfaces/i_drag_target.js'; @@ -42,16 +42,6 @@ interface CandidateConnection { radius: number; } -/** - * An error message to throw if the block created by createMarkerBlock_ is - * missing any components. - */ -const DUPLICATE_BLOCK_ERROR = - 'The insertion marker ' + - 'manager tried to create a marker but the result is missing %1. If ' + - 'you are using a mutator, make sure your domToMutation method is ' + - 'properly defined.'; - /** * Class that controls updates to connections during drags. It is primarily * responsible for finding the closest eligible connection and highlighting or @@ -231,53 +221,30 @@ export class InsertionMarkerManager { * @returns The insertion marker that represents the given block. */ private createMarkerBlock(sourceBlock: BlockSvg): BlockSvg { - const imType = sourceBlock.type; - eventUtils.disable(); let result: BlockSvg; try { - result = this.workspace.newBlock(imType); - result.setInsertionMarker(true); - if (sourceBlock.saveExtraState) { - const state = sourceBlock.saveExtraState(true); - if (state && result.loadExtraState) { - result.loadExtraState(state); - } - } else if (sourceBlock.mutationToDom) { - const oldMutationDom = sourceBlock.mutationToDom(); - if (oldMutationDom && result.domToMutation) { - result.domToMutation(oldMutationDom); - } - } - // Copy field values from the other block. These values may impact the - // rendered size of the insertion marker. Note that we do not care about - // child blocks here. - for (let i = 0; i < sourceBlock.inputList.length; i++) { - const sourceInput = sourceBlock.inputList[i]; - if (sourceInput.name === constants.COLLAPSED_INPUT_NAME) { - continue; // Ignore the collapsed input. - } - const resultInput = result.inputList[i]; - if (!resultInput) { - throw new Error(DUPLICATE_BLOCK_ERROR.replace('%1', 'an input')); - } - for (let j = 0; j < sourceInput.fieldRow.length; j++) { - const sourceField = sourceInput.fieldRow[j]; - const resultField = resultInput.fieldRow[j]; - if (!resultField) { - throw new Error(DUPLICATE_BLOCK_ERROR.replace('%1', 'a field')); - } - resultField.setValue(sourceField.getValue()); - } + const blockJson = blocks.save(sourceBlock, { + addCoordinates: false, + addInputBlocks: false, + addNextBlocks: false, + doFullSerialization: false, + }); + + if (!blockJson) { + throw new Error( + `Failed to serialize source block. ${sourceBlock.toDevString()}`, + ); } + result = blocks.append(blockJson, this.workspace) as BlockSvg; + + // Turn shadow blocks that are created programmatically during + // initalization to insertion markers too. for (const block of result.getDescendants(false)) { block.setInsertionMarker(true); } - result.setCollapsed(sourceBlock.isCollapsed()); - result.setInputsInline(sourceBlock.getInputsInline()); - result.initSvg(); result.getSvgRoot().setAttribute('visibility', 'hidden'); } finally { From 43f6df92a3a7564ad6189f5fca6f077a24400f8e Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Wed, 10 Jan 2024 11:13:30 -0800 Subject: [PATCH 11/92] fix!: `rendered` meaning (#7747) * fix: make rendered strictly for differentiating blocksvgs * chore: fix references to rendered * chore: fix tests * chore: delete TODO --- core/block.ts | 3 +- core/block_svg.ts | 124 +++++++++----------------- core/inputs/input.ts | 4 +- core/insertion_marker_manager.ts | 20 ++--- core/rendered_connection.ts | 45 +++------- tests/mocha/icon_test.js | 54 ----------- tests/mocha/test_helpers/workspace.js | 115 ++++++++++++++++++++---- tests/mocha/workspace_svg_test.js | 44 ++------- 8 files changed, 168 insertions(+), 241 deletions(-) diff --git a/core/block.ts b/core/block.ts index 8f64b20a2..53a1063d8 100644 --- a/core/block.ts +++ b/core/block.ts @@ -202,7 +202,8 @@ export class Block implements IASTNodeLocation, IDeletable { /** Name of the type of hat. */ hat?: string; - rendered: boolean | null = null; + /** Is this block a BlockSVG? */ + readonly rendered: boolean = false; /** * String for block help, or function that returns a URL. Null for no help. diff --git a/core/block_svg.ts b/core/block_svg.ts index f8ac57684..17e6aa3a4 100644 --- a/core/block_svg.ts +++ b/core/block_svg.ts @@ -117,7 +117,10 @@ export class BlockSvg style: BlockStyle; /** @internal */ pathObject: IPathObject; - override rendered = false; + + /** Is this block a BlockSVG? */ + override readonly rendered = true; + private visuallyDisabled = false; /** @@ -650,12 +653,6 @@ export class BlockSvg * @internal */ updateComponentLocations(blockOrigin: Coordinate) { - if (!this.rendered) { - // Rendering is required to lay out the blocks. - // This is probably an invisible block attached to a collapsed block. - return; - } - if (!this.dragging) this.updateConnectionLocations(blockOrigin); this.updateIconLocations(blockOrigin); this.updateFieldLocations(blockOrigin); @@ -789,7 +786,7 @@ export class BlockSvg Tooltip.dispose(); ContextMenu.hide(); - if (animate && this.rendered) { + if (animate) { this.unplug(healStack); blockAnimations.disposeUiEffect(this); } @@ -806,8 +803,6 @@ export class BlockSvg if (this.isDeadOrDying()) return; super.disposeInternal(); - this.rendered = false; - if (common.getSelected() === this) { this.unselect(); this.workspace.cancelCurrentGesture(); @@ -991,13 +986,11 @@ export class BlockSvg if (icon instanceof MutatorIcon) this.mutator = icon; - if (this.rendered) { - icon.initView(this.createIconPointerDownListener(icon)); - icon.applyColour(); - icon.updateEditable(); - this.queueRender(); - this.bumpNeighbours(); - } + icon.initView(this.createIconPointerDownListener(icon)); + icon.applyColour(); + icon.updateEditable(); + this.queueRender(); + this.bumpNeighbours(); return icon; } @@ -1021,10 +1014,9 @@ export class BlockSvg if (type.equals(MutatorIcon.TYPE)) this.mutator = null; - if (this.rendered) { - this.queueRender(); - this.bumpNeighbours(); - } + this.queueRender(); + this.bumpNeighbours(); + return removed; } @@ -1036,7 +1028,7 @@ export class BlockSvg override setEnabled(enabled: boolean) { if (this.isEnabled() !== enabled) { super.setEnabled(enabled); - if (this.rendered && !this.getInheritedDisabled()) { + if (!this.getInheritedDisabled()) { this.updateDisabled(); } } @@ -1049,9 +1041,6 @@ export class BlockSvg * @param highlighted True if highlighted. */ setHighlighted(highlighted: boolean) { - if (!this.rendered) { - return; - } this.pathObject.updateHighlighted(highlighted); } @@ -1183,11 +1172,8 @@ export class BlockSvg opt_check?: string | string[] | null, ) { super.setPreviousStatement(newBoolean, opt_check); - - if (this.rendered) { - this.queueRender(); - this.bumpNeighbours(); - } + this.queueRender(); + this.bumpNeighbours(); } /** @@ -1202,11 +1188,8 @@ export class BlockSvg opt_check?: string | string[] | null, ) { super.setNextStatement(newBoolean, opt_check); - - if (this.rendered) { - this.queueRender(); - this.bumpNeighbours(); - } + this.queueRender(); + this.bumpNeighbours(); } /** @@ -1221,11 +1204,8 @@ export class BlockSvg opt_check?: string | string[] | null, ) { super.setOutput(newBoolean, opt_check); - - if (this.rendered) { - this.queueRender(); - this.bumpNeighbours(); - } + this.queueRender(); + this.bumpNeighbours(); } /** @@ -1235,11 +1215,8 @@ export class BlockSvg */ override setInputsInline(newBoolean: boolean) { super.setInputsInline(newBoolean); - - if (this.rendered) { - this.queueRender(); - this.bumpNeighbours(); - } + this.queueRender(); + this.bumpNeighbours(); } /** @@ -1253,13 +1230,8 @@ export class BlockSvg */ override removeInput(name: string, opt_quiet?: boolean): boolean { const removed = super.removeInput(name, opt_quiet); - - if (this.rendered) { - this.queueRender(); - // Removing an input will cause the block to change shape. - this.bumpNeighbours(); - } - + this.queueRender(); + this.bumpNeighbours(); return removed; } @@ -1271,23 +1243,15 @@ export class BlockSvg */ override moveNumberedInputBefore(inputIndex: number, refIndex: number) { super.moveNumberedInputBefore(inputIndex, refIndex); - - if (this.rendered) { - this.queueRender(); - // Moving an input will cause the block to change shape. - this.bumpNeighbours(); - } + this.queueRender(); + this.bumpNeighbours(); } /** @override */ override appendInput(input: Input): Input { super.appendInput(input); - - if (this.rendered) { - this.queueRender(); - // Adding an input will cause the block to change shape. - this.bumpNeighbours(); - } + this.queueRender(); + this.bumpNeighbours(); return input; } @@ -1341,28 +1305,25 @@ export class BlockSvg * Returns connections originating from this block. * * @param all If true, return all connections even hidden ones. - * Otherwise, for a non-rendered block return an empty list, and for a - * collapsed block don't return inputs connections. + * Otherwise, for a collapsed block don't return inputs connections. * @returns Array of connections. * @internal */ override getConnections_(all: boolean): RenderedConnection[] { const myConnections = []; - if (all || this.rendered) { - if (this.outputConnection) { - myConnections.push(this.outputConnection); - } - if (this.previousConnection) { - myConnections.push(this.previousConnection); - } - if (this.nextConnection) { - myConnections.push(this.nextConnection); - } - if (all || !this.collapsed_) { - for (let i = 0, input; (input = this.inputList[i]); i++) { - if (input.connection) { - myConnections.push(input.connection as RenderedConnection); - } + if (this.outputConnection) { + myConnections.push(this.outputConnection); + } + if (this.previousConnection) { + myConnections.push(this.previousConnection); + } + if (this.nextConnection) { + myConnections.push(this.nextConnection); + } + if (all || !this.collapsed_) { + for (let i = 0, input; (input = this.inputList[i]); i++) { + if (input.connection) { + myConnections.push(input.connection as RenderedConnection); } } } @@ -1573,7 +1534,6 @@ export class BlockSvg * @internal */ renderEfficiently() { - this.rendered = true; dom.startTextWidthCache(); if (this.isCollapsed()) { diff --git a/core/inputs/input.ts b/core/inputs/input.ts index 96498ec1b..a94102aca 100644 --- a/core/inputs/input.ts +++ b/core/inputs/input.ts @@ -124,7 +124,6 @@ export class Input { if (this.sourceBlock.rendered) { (this.sourceBlock as BlockSvg).queueRender(); - // Adding a field will cause the block to change shape. this.sourceBlock.bumpNeighbours(); } return index; @@ -146,7 +145,6 @@ export class Input { this.fieldRow.splice(i, 1); if (this.sourceBlock.rendered) { (this.sourceBlock as BlockSvg).queueRender(); - // Removing a field will cause the block to change shape. this.sourceBlock.bumpNeighbours(); } return true; @@ -274,7 +272,7 @@ export class Input { /** Initialize the fields on this input. */ init() { - if (!this.sourceBlock.workspace.rendered) { + if (!this.sourceBlock.rendered) { return; // Headless blocks don't need fields initialized. } for (let i = 0; i < this.fieldRow.length; i++) { diff --git a/core/insertion_marker_manager.ts b/core/insertion_marker_manager.ts index cc507bd70..302dd47c6 100644 --- a/core/insertion_marker_manager.ts +++ b/core/insertion_marker_manager.ts @@ -176,18 +176,16 @@ export class InsertionMarkerManager { eventUtils.enable(); const {local, closest} = this.activeCandidate; local.connect(closest); - if (this.topBlock.rendered) { - const inferiorConnection = local.isSuperior() ? closest : local; - const rootBlock = this.topBlock.getRootBlock(); + const inferiorConnection = local.isSuperior() ? closest : local; + const rootBlock = this.topBlock.getRootBlock(); - finishQueuedRenders().then(() => { - blockAnimations.connectionUiEffect(inferiorConnection.getSourceBlock()); - // bringToFront is incredibly expensive. Delay until the next frame. - setTimeout(() => { - rootBlock.bringToFront(); - }, 0); - }); - } + finishQueuedRenders().then(() => { + blockAnimations.connectionUiEffect(inferiorConnection.getSourceBlock()); + // bringToFront is incredibly expensive. Delay until the next frame. + setTimeout(() => { + rootBlock.bringToFront(); + }, 0); + }); } /** diff --git a/core/rendered_connection.ts b/core/rendered_connection.ts index 08c0471a2..e68450883 100644 --- a/core/rendered_connection.ts +++ b/core/rendered_connection.ts @@ -502,16 +502,12 @@ export class RenderedConnection extends Connection { const parent = parentConnection.getSourceBlock() as BlockSvg; const child = childConnection.getSourceBlock() as BlockSvg; super.disconnectInternal(setParent); - // Rerender the parent so that it may reflow. - if (parent.rendered) { - parent.queueRender(); - } - if (child.rendered) { - child.updateDisabled(); - child.queueRender(); - // Reset visibility, since the child is now a top block. - child.getSvgRoot().style.display = 'block'; - } + + parent.queueRender(); + child.updateDisabled(); + child.queueRender(); + // Reset visibility, since the child is now a top block. + child.getSvgRoot().style.display = 'block'; } /** @@ -554,29 +550,10 @@ export class RenderedConnection extends Connection { const parentBlock = this.getSourceBlock(); const childBlock = renderedChildConnection.getSourceBlock(); - const parentRendered = parentBlock.rendered; - const childRendered = childBlock.rendered; - if (parentRendered) { - parentBlock.updateDisabled(); - } - if (childRendered) { - childBlock.updateDisabled(); - } - if (parentRendered && childRendered) { - if ( - this.type === ConnectionType.NEXT_STATEMENT || - this.type === ConnectionType.PREVIOUS_STATEMENT - ) { - // Child block may need to square off its corners if it is in a stack. - // Rendering a child will render its parent. - childBlock.queueRender(); - } else { - // Child block does not change shape. Rendering the parent node will - // move its connected children into position. - parentBlock.queueRender(); - } - } + parentBlock.updateDisabled(); + childBlock.updateDisabled(); + childBlock.queueRender(); // The input the child block is connected to (if any). const parentInput = parentBlock.getInputWithBlock(childBlock); @@ -617,9 +594,7 @@ export class RenderedConnection extends Connection { */ override setCheck(check: string | string[] | null): RenderedConnection { super.setCheck(check); - if (this.sourceBlock_.rendered) { - this.sourceBlock_.queueRender(); - } + this.sourceBlock_.queueRender(); return this; } } diff --git a/tests/mocha/icon_test.js b/tests/mocha/icon_test.js index 4ff27997f..63c723495 100644 --- a/tests/mocha/icon_test.js +++ b/tests/mocha/icon_test.js @@ -68,24 +68,6 @@ suite('Icon', function () { ); }); - test('initView is called by headful blocks during initSvg', function () { - const workspace = createWorkspaceSvg(); - const block = createUninitializedBlock(workspace); - const icon = new MockIcon(); - const initViewSpy = sinon.spy(icon, 'initView'); - - block.addIcon(icon); - chai.assert.isFalse( - initViewSpy.called, - 'Expected initView to not be called before initing svg', - ); - block.initSvg(); - chai.assert.isTrue( - initViewSpy.calledOnce, - 'Expected initView to be called', - ); - }); - test( 'initView is called by headful blocks that are currently ' + 'rendered when the icon is added', @@ -120,24 +102,6 @@ suite('Icon', function () { ); }); - test('applyColour is called by headful blocks during initSvg', function () { - const workspace = createWorkspaceSvg(); - const block = createUninitializedBlock(workspace); - const icon = new MockIcon(); - const applyColourSpy = sinon.spy(icon, 'applyColour'); - - block.addIcon(icon); - chai.assert.isFalse( - applyColourSpy.called, - 'Expected applyCOlour to not be called before initing svg', - ); - block.initSvg(); - chai.assert.isTrue( - applyColourSpy.calledOnce, - 'Expected applyColour to be called', - ); - }); - test( 'applyColour is called by headful blocks that are currently ' + 'rendered when the icon is added', @@ -231,24 +195,6 @@ suite('Icon', function () { ); }); - test('updateEditable is called by headful blocks during initSvg', function () { - const workspace = createWorkspaceSvg(); - const block = createUninitializedBlock(workspace); - const icon = new MockIcon(); - const updateEditableSpy = sinon.spy(icon, 'updateEditable'); - - block.addIcon(icon); - chai.assert.isFalse( - updateEditableSpy.called, - 'Expected updateEditable to not be called before initing svg', - ); - block.initSvg(); - chai.assert.isTrue( - updateEditableSpy.calledOnce, - 'Expected updateEditable to be called', - ); - }); - test( 'updateEditable is called by headful blocks that are currently ' + 'rendered when the icon is added', diff --git a/tests/mocha/test_helpers/workspace.js b/tests/mocha/test_helpers/workspace.js index 7654feba2..6da113f8b 100644 --- a/tests/mocha/test_helpers/workspace.js +++ b/tests/mocha/test_helpers/workspace.js @@ -781,7 +781,9 @@ export function testAWorkspace() { const xml = Blockly.utils.xml.textToDom(xmlText); Blockly.Xml.domToBlock(xml, this.workspace); this.workspace.getTopBlocks()[0].dispose(false); + this.clock.runAll(); this.workspace.undo(); + this.clock.runAll(); const newXml = Blockly.Xml.workspaceToDom(this.workspace); assertNodesEqual(newXml.firstChild, xml); } @@ -909,11 +911,14 @@ export function testAWorkspace() { function testUndoConnect(xmlText, parentId, childId, func) { const xml = Blockly.utils.xml.textToDom(xmlText); Blockly.Xml.domToWorkspace(xml, this.workspace); + this.clock.runAll(); const parent = this.workspace.getBlockById(parentId); const child = this.workspace.getBlockById(childId); func.call(this, parent, child); + this.clock.runAll(); this.workspace.undo(); + this.clock.runAll(); const newXml = Blockly.Xml.workspaceToDom(this.workspace); assertNodesEqual(newXml, xml); @@ -922,8 +927,8 @@ export function testAWorkspace() { test('Stack', function () { const xml = '' + - ' ' + - ' ' + + ' ' + + ' ' + ''; testUndoConnect.call(this, xml, '1', '2', (parent, child) => { @@ -934,8 +939,8 @@ export function testAWorkspace() { test('Row', function () { const xml = '' + - ' ' + - ' ' + + ' ' + + ' ' + ''; testUndoConnect.call(this, xml, '1', '2', (parent, child) => { @@ -946,8 +951,8 @@ export function testAWorkspace() { test('Statement', function () { const xml = '' + - ' ' + - ' ' + + ' ' + + ' ' + ''; testUndoConnect.call(this, xml, '1', '2', (parent, child) => { @@ -960,12 +965,12 @@ export function testAWorkspace() { test('Stack w/ child', function () { const xml = '' + - ' ' + + ' ' + ' ' + ' ' + ' ' + ' ' + - ' ' + + ' ' + ''; testUndoConnect.call(this, xml, '1', '2', (parent, child) => { @@ -976,12 +981,12 @@ export function testAWorkspace() { test('Row w/ child', function () { const xml = '' + - ' ' + + ' ' + ' ' + ' ' + ' ' + ' ' + - ' ' + + ' ' + ''; testUndoConnect.call(this, xml, '1', '2', (parent, child) => { @@ -992,12 +997,12 @@ export function testAWorkspace() { test('Statement w/ child', function () { const xml = '' + - ' ' + + ' ' + ' ' + ' ' + ' ' + ' ' + - ' ' + + ' ' + ''; testUndoConnect.call(this, xml, '1', '2', (parent, child) => { @@ -1010,12 +1015,12 @@ export function testAWorkspace() { test('Stack w/ shadow', function () { const xml = '' + - ' ' + + ' ' + ' ' + ' ' + ' ' + ' ' + - ' ' + + ' ' + ''; testUndoConnect.call(this, xml, '1', '2', (parent, child) => { @@ -1026,12 +1031,12 @@ export function testAWorkspace() { test('Row w/ shadow', function () { const xml = '' + - ' ' + + ' ' + ' ' + ' ' + ' ' + ' ' + - ' ' + + ' ' + ''; testUndoConnect.call(this, xml, '1', '2', (parent, child) => { @@ -1042,12 +1047,12 @@ export function testAWorkspace() { test('Statement w/ shadow', function () { const xml = '' + - ' ' + + ' ' + ' ' + ' ' + ' ' + ' ' + - ' ' + + ' ' + ''; testUndoConnect.call(this, xml, '1', '2', (parent, child) => { @@ -1102,6 +1107,7 @@ export function testAWorkspace() { function testUndoDisconnect(xmlText, childId) { const xml = Blockly.utils.xml.textToDom(xmlText); Blockly.Xml.domToWorkspace(xml, this.workspace); + this.clock.runAll(); const child = this.workspace.getBlockById(childId); if (child.outputConnection) { @@ -1109,7 +1115,9 @@ export function testAWorkspace() { } else { child.previousConnection.disconnect(); } + this.clock.runAll(); this.workspace.undo(); + this.clock.runAll(); const newXml = Blockly.Xml.workspaceToDom(this.workspace); assertNodesEqual(newXml, xml); @@ -1262,8 +1270,10 @@ export function testAWorkspace() { suite('createVariable', function () { test('Undo only', function () { createTwoVarsDifferentTypes(this.workspace); + this.clock.runAll(); this.workspace.undo(); + this.clock.runAll(); assertVariableValues(this.workspace, 'name1', 'type1', 'id1'); chai.assert.isNull(this.workspace.getVariableById('id2')); @@ -1274,8 +1284,10 @@ export function testAWorkspace() { test('Undo and redo', function () { createTwoVarsDifferentTypes(this.workspace); + this.clock.runAll(); this.workspace.undo(); + this.clock.runAll(); assertVariableValues(this.workspace, 'name1', 'type1', 'id1'); chai.assert.isNull(this.workspace.getVariableById('id2')); @@ -1300,14 +1312,18 @@ export function testAWorkspace() { suite('deleteVariableById', function () { test('Undo only no usages', function () { createTwoVarsDifferentTypes(this.workspace); + this.clock.runAll(); this.workspace.deleteVariableById('id1'); this.workspace.deleteVariableById('id2'); + this.clock.runAll(); this.workspace.undo(); + this.clock.runAll(); chai.assert.isNull(this.workspace.getVariableById('id1')); assertVariableValues(this.workspace, 'name2', 'type2', 'id2'); this.workspace.undo(); + this.clock.runAll(); assertVariableValues(this.workspace, 'name1', 'type1', 'id1'); assertVariableValues(this.workspace, 'name2', 'type2', 'id2'); }); @@ -1316,15 +1332,19 @@ export function testAWorkspace() { createTwoVarsDifferentTypes(this.workspace); // Create blocks to refer to both of them. createVarBlocksNoEvents(this.workspace, ['id1', 'id2']); + this.clock.runAll(); this.workspace.deleteVariableById('id1'); this.workspace.deleteVariableById('id2'); + this.clock.runAll(); this.workspace.undo(); + this.clock.runAll(); assertBlockVarModelName(this.workspace, 0, 'name2'); chai.assert.isNull(this.workspace.getVariableById('id1')); assertVariableValues(this.workspace, 'name2', 'type2', 'id2'); this.workspace.undo(); + this.clock.runAll(); assertBlockVarModelName(this.workspace, 0, 'name2'); assertBlockVarModelName(this.workspace, 1, 'name1'); assertVariableValues(this.workspace, 'name1', 'type1', 'id1'); @@ -1333,24 +1353,31 @@ export function testAWorkspace() { test('Reference exists no usages', function () { createTwoVarsDifferentTypes(this.workspace); + this.clock.runAll(); this.workspace.deleteVariableById('id1'); this.workspace.deleteVariableById('id2'); + this.clock.runAll(); this.workspace.undo(); + this.clock.runAll(); chai.assert.isNull(this.workspace.getVariableById('id1')); assertVariableValues(this.workspace, 'name2', 'type2', 'id2'); this.workspace.undo(true); + this.clock.runAll(); // Expect that both variables are deleted chai.assert.isNull(this.workspace.getVariableById('id1')); chai.assert.isNull(this.workspace.getVariableById('id2')); this.workspace.undo(); + this.clock.runAll(); this.workspace.undo(); + this.clock.runAll(); assertVariableValues(this.workspace, 'name1', 'type1', 'id1'); assertVariableValues(this.workspace, 'name2', 'type2', 'id2'); this.workspace.undo(true); + this.clock.runAll(); // Expect that variable 'id2' is recreated chai.assert.isNull(this.workspace.getVariableById('id1')); assertVariableValues(this.workspace, 'name2', 'type2', 'id2'); @@ -1360,28 +1387,35 @@ export function testAWorkspace() { createTwoVarsDifferentTypes(this.workspace); // Create blocks to refer to both of them. createVarBlocksNoEvents(this.workspace, ['id1', 'id2']); + this.clock.runAll(); this.workspace.deleteVariableById('id1'); this.workspace.deleteVariableById('id2'); + this.clock.runAll(); this.workspace.undo(); + this.clock.runAll(); assertBlockVarModelName(this.workspace, 0, 'name2'); chai.assert.isNull(this.workspace.getVariableById('id1')); assertVariableValues(this.workspace, 'name2', 'type2', 'id2'); this.workspace.undo(true); + this.clock.runAll(); // Expect that both variables are deleted chai.assert.equal(this.workspace.getTopBlocks(false).length, 0); chai.assert.isNull(this.workspace.getVariableById('id1')); chai.assert.isNull(this.workspace.getVariableById('id2')); this.workspace.undo(); + this.clock.runAll(); this.workspace.undo(); + this.clock.runAll(); assertBlockVarModelName(this.workspace, 0, 'name2'); assertBlockVarModelName(this.workspace, 1, 'name1'); assertVariableValues(this.workspace, 'name1', 'type1', 'id1'); assertVariableValues(this.workspace, 'name2', 'type2', 'id2'); this.workspace.undo(true); + this.clock.runAll(); // Expect that variable 'id2' is recreated assertBlockVarModelName(this.workspace, 0, 'name2'); chai.assert.isNull(this.workspace.getVariableById('id1')); @@ -1391,6 +1425,7 @@ export function testAWorkspace() { test('Delete same variable twice no usages', function () { this.workspace.createVariable('name1', 'type1', 'id1'); this.workspace.deleteVariableById('id1'); + this.clock.runAll(); const workspace = this.workspace; assertWarnings(() => { workspace.deleteVariableById('id1'); @@ -1406,21 +1441,26 @@ export function testAWorkspace() { // Undo delete this.workspace.undo(); + this.clock.runAll(); assertVariableValues(this.workspace, 'name1', 'type1', 'id1'); // Redo delete this.workspace.undo(true); + this.clock.runAll(); chai.assert.isNull(this.workspace.getVariableById('id1')); // Redo delete, nothing should happen this.workspace.undo(true); + this.clock.runAll(); chai.assert.isNull(this.workspace.getVariableById('id1')); }); test('Delete same variable twice with usages', function () { this.workspace.createVariable('name1', 'type1', 'id1'); createVarBlocksNoEvents(this.workspace, ['id1']); + this.clock.runAll(); this.workspace.deleteVariableById('id1'); + this.clock.runAll(); const workspace = this.workspace; assertWarnings(() => { workspace.deleteVariableById('id1'); @@ -1437,16 +1477,19 @@ export function testAWorkspace() { // Undo delete this.workspace.undo(); + this.clock.runAll(); assertBlockVarModelName(this.workspace, 0, 'name1'); assertVariableValues(this.workspace, 'name1', 'type1', 'id1'); // Redo delete this.workspace.undo(true); + this.clock.runAll(); chai.assert.equal(this.workspace.getTopBlocks(false).length, 0); chai.assert.isNull(this.workspace.getVariableById('id1')); // Redo delete, nothing should happen this.workspace.undo(true); + this.clock.runAll(); chai.assert.equal(this.workspace.getTopBlocks(false).length, 0); chai.assert.isNull(this.workspace.getVariableById('id1')); }); @@ -1459,46 +1502,58 @@ export function testAWorkspace() { test('Reference exists no usages rename to name2', function () { this.workspace.renameVariableById('id1', 'name2'); + this.clock.runAll(); this.workspace.undo(); + this.clock.runAll(); assertVariableValues(this.workspace, 'name1', 'type1', 'id1'); this.workspace.undo(true); + this.clock.runAll(); assertVariableValues(this.workspace, 'name2', 'type1', 'id1'); }); test('Reference exists with usages rename to name2', function () { createVarBlocksNoEvents(this.workspace, ['id1']); this.workspace.renameVariableById('id1', 'name2'); + this.clock.runAll(); this.workspace.undo(); + this.clock.runAll(); assertBlockVarModelName(this.workspace, 0, 'name1'); assertVariableValues(this.workspace, 'name1', 'type1', 'id1'); this.workspace.undo(true); + this.clock.runAll(); assertBlockVarModelName(this.workspace, 0, 'name2'); assertVariableValues(this.workspace, 'name2', 'type1', 'id1'); }); test('Reference exists different capitalization no usages rename to Name1', function () { this.workspace.renameVariableById('id1', 'Name1'); + this.clock.runAll(); this.workspace.undo(); + this.clock.runAll(); assertVariableValues(this.workspace, 'name1', 'type1', 'id1'); this.workspace.undo(true); + this.clock.runAll(); assertVariableValues(this.workspace, 'Name1', 'type1', 'id1'); }); test('Reference exists different capitalization with usages rename to Name1', function () { createVarBlocksNoEvents(this.workspace, ['id1']); this.workspace.renameVariableById('id1', 'Name1'); + this.clock.runAll(); this.workspace.undo(); + this.clock.runAll(); assertBlockVarModelName(this.workspace, 0, 'name1'); assertVariableValues(this.workspace, 'name1', 'type1', 'id1'); this.workspace.undo(true); + this.clock.runAll(); assertBlockVarModelName(this.workspace, 0, 'Name1'); assertVariableValues(this.workspace, 'Name1', 'type1', 'id1'); }); @@ -1507,12 +1562,15 @@ export function testAWorkspace() { test('Same type no usages rename variable with id1 to name2', function () { this.workspace.createVariable('name2', 'type1', 'id2'); this.workspace.renameVariableById('id1', 'name2'); + this.clock.runAll(); this.workspace.undo(); + this.clock.runAll(); assertVariableValues(this.workspace, 'name1', 'type1', 'id1'); assertVariableValues(this.workspace, 'name2', 'type1', 'id2'); this.workspace.undo(true); + this.clock.runAll(); assertVariableValues(this.workspace, 'name2', 'type1', 'id2'); chai.assert.isNull(this.workspace.getVariableById('id1')); }); @@ -1521,14 +1579,17 @@ export function testAWorkspace() { this.workspace.createVariable('name2', 'type1', 'id2'); createVarBlocksNoEvents(this.workspace, ['id1', 'id2']); this.workspace.renameVariableById('id1', 'name2'); + this.clock.runAll(); this.workspace.undo(); + this.clock.runAll(); assertBlockVarModelName(this.workspace, 0, 'name1'); assertBlockVarModelName(this.workspace, 1, 'name2'); assertVariableValues(this.workspace, 'name1', 'type1', 'id1'); assertVariableValues(this.workspace, 'name2', 'type1', 'id2'); this.workspace.undo(true); + this.clock.runAll(); assertVariableValues(this.workspace, 'name2', 'type1', 'id2'); chai.assert.isNull(this.workspace.getVariableById('id1')); }); @@ -1536,12 +1597,15 @@ export function testAWorkspace() { test('Same type different capitalization no usages rename variable with id1 to Name2', function () { this.workspace.createVariable('name2', 'type1', 'id2'); this.workspace.renameVariableById('id1', 'Name2'); + this.clock.runAll(); this.workspace.undo(); + this.clock.runAll(); assertVariableValues(this.workspace, 'name1', 'type1', 'id1'); assertVariableValues(this.workspace, 'name2', 'type1', 'id2'); this.workspace.undo(true); + this.clock.runAll(); assertVariableValues(this.workspace, 'Name2', 'type1', 'id2'); chai.assert.isNull(this.workspace.getVariable('name1')); }); @@ -1550,14 +1614,17 @@ export function testAWorkspace() { this.workspace.createVariable('name2', 'type1', 'id2'); createVarBlocksNoEvents(this.workspace, ['id1', 'id2']); this.workspace.renameVariableById('id1', 'Name2'); + this.clock.runAll(); this.workspace.undo(); + this.clock.runAll(); assertBlockVarModelName(this.workspace, 0, 'name1'); assertBlockVarModelName(this.workspace, 1, 'name2'); assertVariableValues(this.workspace, 'name1', 'type1', 'id1'); assertVariableValues(this.workspace, 'name2', 'type1', 'id2'); this.workspace.undo(true); + this.clock.runAll(); assertVariableValues(this.workspace, 'Name2', 'type1', 'id2'); chai.assert.isNull(this.workspace.getVariableById('id1')); assertBlockVarModelName(this.workspace, 0, 'Name2'); @@ -1567,12 +1634,15 @@ export function testAWorkspace() { test('Different type no usages rename variable with id1 to name2', function () { this.workspace.createVariable('name2', 'type2', 'id2'); this.workspace.renameVariableById('id1', 'name2'); + this.clock.runAll(); this.workspace.undo(); + this.clock.runAll(); assertVariableValues(this.workspace, 'name1', 'type1', 'id1'); assertVariableValues(this.workspace, 'name2', 'type2', 'id2'); this.workspace.undo(true); + this.clock.runAll(); assertVariableValues(this.workspace, 'name2', 'type1', 'id1'); assertVariableValues(this.workspace, 'name2', 'type2', 'id2'); }); @@ -1581,14 +1651,17 @@ export function testAWorkspace() { this.workspace.createVariable('name2', 'type2', 'id2'); createVarBlocksNoEvents(this.workspace, ['id1', 'id2']); this.workspace.renameVariableById('id1', 'name2'); + this.clock.runAll(); this.workspace.undo(); + this.clock.runAll(); assertVariableValues(this.workspace, 'name1', 'type1', 'id1'); assertVariableValues(this.workspace, 'name2', 'type2', 'id2'); assertBlockVarModelName(this.workspace, 0, 'name1'); assertBlockVarModelName(this.workspace, 1, 'name2'); this.workspace.undo(true); + this.clock.runAll(); assertVariableValues(this.workspace, 'name2', 'type1', 'id1'); assertVariableValues(this.workspace, 'name2', 'type2', 'id2'); assertBlockVarModelName(this.workspace, 0, 'name2'); @@ -1598,12 +1671,15 @@ export function testAWorkspace() { test('Different type different capitalization no usages rename variable with id1 to Name2', function () { this.workspace.createVariable('name2', 'type2', 'id2'); this.workspace.renameVariableById('id1', 'Name2'); + this.clock.runAll(); this.workspace.undo(); + this.clock.runAll(); assertVariableValues(this.workspace, 'name1', 'type1', 'id1'); assertVariableValues(this.workspace, 'name2', 'type2', 'id2'); this.workspace.undo(true); + this.clock.runAll(); assertVariableValues(this.workspace, 'Name2', 'type1', 'id1'); assertVariableValues(this.workspace, 'name2', 'type2', 'id2'); }); @@ -1612,14 +1688,17 @@ export function testAWorkspace() { this.workspace.createVariable('name2', 'type2', 'id2'); createVarBlocksNoEvents(this.workspace, ['id1', 'id2']); this.workspace.renameVariableById('id1', 'Name2'); + this.clock.runAll(); this.workspace.undo(); + this.clock.runAll(); assertVariableValues(this.workspace, 'name1', 'type1', 'id1'); assertVariableValues(this.workspace, 'name2', 'type2', 'id2'); assertBlockVarModelName(this.workspace, 0, 'name1'); assertBlockVarModelName(this.workspace, 1, 'name2'); this.workspace.undo(true); + this.clock.runAll(); assertVariableValues(this.workspace, 'Name2', 'type1', 'id1'); assertVariableValues(this.workspace, 'name2', 'type2', 'id2'); assertBlockVarModelName(this.workspace, 0, 'Name2'); diff --git a/tests/mocha/workspace_svg_test.js b/tests/mocha/workspace_svg_test.js index cea58cdf9..98ca14d57 100644 --- a/tests/mocha/workspace_svg_test.js +++ b/tests/mocha/workspace_svg_test.js @@ -21,7 +21,7 @@ import {testAWorkspace} from './test_helpers/workspace.js'; suite('WorkspaceSvg', function () { setup(function () { - sharedTestSetup.call(this); + this.clock = sharedTestSetup.call(this, {fireEventsNow: false}).clock; const toolbox = document.getElementById('toolbox-categories'); this.workspace = Blockly.inject('blocklyDiv', {toolbox: toolbox}); Blockly.defineBlocksWithJsonArray([ @@ -168,8 +168,7 @@ suite('WorkspaceSvg', function () { }); suite('Viewport change events', function () { - function resetEventHistory(eventsFireStub, changeListenerSpy) { - eventsFireStub.resetHistory(); + function resetEventHistory(changeListenerSpy) { changeListenerSpy.resetHistory(); } function assertSpyFiredViewportEvent(spy, workspace, expectedProperties) { @@ -187,7 +186,6 @@ suite('WorkspaceSvg', function () { ); } function assertViewportEventFired( - eventsFireStub, changeListenerSpy, workspace, expectedEventCount = 1, @@ -200,32 +198,25 @@ suite('WorkspaceSvg', function () { viewLeft: metrics.viewLeft, type: eventUtils.VIEWPORT_CHANGE, }; - assertSpyFiredViewportEvent( - eventsFireStub, - workspace, - expectedProperties, - ); assertSpyFiredViewportEvent( changeListenerSpy, workspace, expectedProperties, ); sinon.assert.callCount(changeListenerSpy, expectedEventCount); - sinon.assert.callCount(eventsFireStub, expectedEventCount); } function runViewportEventTest( eventTriggerFunc, - eventsFireStub, changeListenerSpy, workspace, clock, expectedEventCount = 1, ) { clock.runAll(); - resetEventHistory(eventsFireStub, changeListenerSpy); + resetEventHistory(changeListenerSpy); eventTriggerFunc(); + clock.runAll(); assertViewportEventFired( - eventsFireStub, changeListenerSpy, workspace, expectedEventCount, @@ -243,7 +234,6 @@ suite('WorkspaceSvg', function () { test('setScale', function () { runViewportEventTest( () => this.workspace.setScale(2), - this.eventsFireStub, this.changeListenerSpy, this.workspace, this.clock, @@ -252,7 +242,6 @@ suite('WorkspaceSvg', function () { test('zoom(50, 50, 1)', function () { runViewportEventTest( () => this.workspace.zoom(50, 50, 1), - this.eventsFireStub, this.changeListenerSpy, this.workspace, this.clock, @@ -261,7 +250,6 @@ suite('WorkspaceSvg', function () { test('zoom(50, 50, -1)', function () { runViewportEventTest( () => this.workspace.zoom(50, 50, -1), - this.eventsFireStub, this.changeListenerSpy, this.workspace, this.clock, @@ -270,7 +258,6 @@ suite('WorkspaceSvg', function () { test('zoomCenter(1)', function () { runViewportEventTest( () => this.workspace.zoomCenter(1), - this.eventsFireStub, this.changeListenerSpy, this.workspace, this.clock, @@ -279,7 +266,6 @@ suite('WorkspaceSvg', function () { test('zoomCenter(-1)', function () { runViewportEventTest( () => this.workspace.zoomCenter(-1), - this.eventsFireStub, this.changeListenerSpy, this.workspace, this.clock, @@ -291,7 +277,6 @@ suite('WorkspaceSvg', function () { block.render(); runViewportEventTest( () => this.workspace.zoomToFit(), - this.eventsFireStub, this.changeListenerSpy, this.workspace, this.clock, @@ -305,7 +290,6 @@ suite('WorkspaceSvg', function () { block.render(); runViewportEventTest( () => this.workspace.centerOnBlock(block.id), - this.eventsFireStub, this.changeListenerSpy, this.workspace, this.clock, @@ -314,7 +298,6 @@ suite('WorkspaceSvg', function () { test('scroll', function () { runViewportEventTest( () => this.workspace.scroll(50, 50), - this.eventsFireStub, this.changeListenerSpy, this.workspace, this.clock, @@ -323,7 +306,6 @@ suite('WorkspaceSvg', function () { test('scrollCenter', function () { runViewportEventTest( () => this.workspace.scrollCenter(), - this.eventsFireStub, this.changeListenerSpy, this.workspace, this.clock, @@ -336,13 +318,12 @@ suite('WorkspaceSvg', function () { block.initSvg(); block.render(); this.clock.runAll(); - resetEventHistory(this.eventsFireStub, this.changeListenerSpy); + resetEventHistory(this.changeListenerSpy); // Expect 2 events, 1 move, 1 viewport runViewportEventTest( () => { block.moveBy(1000, 1000); }, - this.eventsFireStub, this.changeListenerSpy, this.workspace, this.clock, @@ -366,7 +347,7 @@ suite('WorkspaceSvg', function () { '', ); this.clock.runAll(); - resetEventHistory(this.eventsFireStub, this.changeListenerSpy); + resetEventHistory(this.changeListenerSpy); // Add block in center of other blocks, not triggering scroll. Blockly.Xml.domToWorkspace( Blockly.utils.xml.textToDom( @@ -375,11 +356,6 @@ suite('WorkspaceSvg', function () { this.workspace, ); this.clock.runAll(); - assertEventNotFired( - this.eventsFireStub, - Blockly.Events.ViewportChange, - {type: eventUtils.VIEWPORT_CHANGE}, - ); assertEventNotFired( this.changeListenerSpy, Blockly.Events.ViewportChange, @@ -403,15 +379,10 @@ suite('WorkspaceSvg', function () { '', ); this.clock.runAll(); - resetEventHistory(this.eventsFireStub, this.changeListenerSpy); + resetEventHistory(this.changeListenerSpy); // Add block in center of other blocks, not triggering scroll. Blockly.Xml.domToWorkspace(xmlDom, this.workspace); this.clock.runAll(); - assertEventNotFired( - this.eventsFireStub, - Blockly.Events.ViewportChange, - {type: eventUtils.VIEWPORT_CHANGE}, - ); assertEventNotFired( this.changeListenerSpy, Blockly.Events.ViewportChange, @@ -436,7 +407,6 @@ suite('WorkspaceSvg', function () { // Expect 10 events, 4 create, 4 move, 1 viewport, 1 finished loading runViewportEventTest( addingMultipleBlocks, - this.eventsFireStub, this.changeListenerSpy, this.workspace, this.clock, From 8c5f32b2f9068159ae835f3faa1b650472806a48 Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Wed, 17 Jan 2024 10:48:04 -0800 Subject: [PATCH 12/92] fix: bump neighbours performance regression (#7748) * fix: move bumping neighbours to the end of rendering * chore: remove scheduleSnapAndBump * chore: remove references to bumpNeighbours * chore: work on fixing tests * fix: bump neighbours event grouping * chore: format * chore: readd deprecation import * fix: move event ordering * chore: undeprecate bumpNeighbours * fix: bumping during drag due to insertion markers * chore: tests * chore: PR feedback * chore: docs * chore: typo --- core/block.ts | 1 - core/block_dragger.ts | 2 +- core/block_svg.ts | 42 ++------------------------- core/field.ts | 1 - core/gesture.ts | 13 ++++++--- core/inputs/input.ts | 2 -- core/render_management.ts | 17 ++++++++++- core/rendered_connection.ts | 7 +++-- tests/mocha/input_test.js | 8 ----- tests/mocha/render_management_test.js | 1 + 10 files changed, 35 insertions(+), 59 deletions(-) diff --git a/core/block.ts b/core/block.ts index 53a1063d8..becc8c91b 100644 --- a/core/block.ts +++ b/core/block.ts @@ -560,7 +560,6 @@ export class Block implements IASTNodeLocation, IDeletable { * connected should not coincidentally line up on screen. */ bumpNeighbours() {} - // noop. /** * Return the parent block or null if this block is at the top level. The diff --git a/core/block_dragger.ts b/core/block_dragger.ts index ef061b6d9..0cefc4cfb 100644 --- a/core/block_dragger.ts +++ b/core/block_dragger.ts @@ -293,7 +293,7 @@ export class BlockDragger implements IBlockDragger { } else { this.draggingBlock_.queueRender(); } - this.draggingBlock_.scheduleSnapAndBump(); + this.draggingBlock_.snapToGrid(); } /** Fire a UI event at the end of a block drag. */ diff --git a/core/block_svg.ts b/core/block_svg.ts index 17e6aa3a4..5dcb1d4bd 100644 --- a/core/block_svg.ts +++ b/core/block_svg.ts @@ -142,12 +142,6 @@ export class BlockSvg private translation = ''; - /** - * The ID of the setTimeout callback for bumping neighbours, or 0 if no bump - * is currently scheduled. - */ - private bumpNeighboursPid = 0; - /** Whether this block is currently being dragged. */ private dragging = false; @@ -990,7 +984,6 @@ export class BlockSvg icon.applyColour(); icon.updateEditable(); this.queueRender(); - this.bumpNeighbours(); return icon; } @@ -1015,7 +1008,6 @@ export class BlockSvg if (type.equals(MutatorIcon.TYPE)) this.mutator = null; this.queueRender(); - this.bumpNeighbours(); return removed; } @@ -1173,7 +1165,6 @@ export class BlockSvg ) { super.setPreviousStatement(newBoolean, opt_check); this.queueRender(); - this.bumpNeighbours(); } /** @@ -1189,7 +1180,6 @@ export class BlockSvg ) { super.setNextStatement(newBoolean, opt_check); this.queueRender(); - this.bumpNeighbours(); } /** @@ -1205,7 +1195,6 @@ export class BlockSvg ) { super.setOutput(newBoolean, opt_check); this.queueRender(); - this.bumpNeighbours(); } /** @@ -1216,7 +1205,6 @@ export class BlockSvg override setInputsInline(newBoolean: boolean) { super.setInputsInline(newBoolean); this.queueRender(); - this.bumpNeighbours(); } /** @@ -1231,7 +1219,6 @@ export class BlockSvg override removeInput(name: string, opt_quiet?: boolean): boolean { const removed = super.removeInput(name, opt_quiet); this.queueRender(); - this.bumpNeighbours(); return removed; } @@ -1244,14 +1231,12 @@ export class BlockSvg override moveNumberedInputBefore(inputIndex: number, refIndex: number) { super.moveNumberedInputBefore(inputIndex, refIndex); this.queueRender(); - this.bumpNeighbours(); } /** @override */ override appendInput(input: Input): Input { super.appendInput(input); this.queueRender(); - this.bumpNeighbours(); return input; } @@ -1399,22 +1384,6 @@ export class BlockSvg * up on screen, because that creates confusion for end-users. */ override bumpNeighbours() { - if (this.bumpNeighboursPid) return; - const group = eventUtils.getGroup(); - - this.bumpNeighboursPid = setTimeout(() => { - const oldGroup = eventUtils.getGroup(); - eventUtils.setGroup(group); - this.getRootBlock().bumpNeighboursInternal(); - eventUtils.setGroup(oldGroup); - this.bumpNeighboursPid = 0; - }, config.bumpDelay); - } - - /** - * Bumps unconnected blocks out of alignment. - */ - private bumpNeighboursInternal() { const root = this.getRootBlock(); if ( this.isDeadOrDying() || @@ -1431,16 +1400,13 @@ export class BlockSvg for (const conn of this.getConnections_(false)) { if (conn.isSuperior()) { // Recurse down the block stack. - conn.targetBlock()?.bumpNeighboursInternal(); + conn.targetBlock()?.bumpNeighbours(); } for (const neighbour of conn.neighbours(config.snapRadius)) { - // Don't bump away from things that are in our stack. if (neighbourIsInStack(neighbour)) continue; - // If both connections are connected, that's fine. if (conn.isConnected() && neighbour.isConnected()) continue; - // Always bump the inferior connection. if (conn.isSuperior()) { neighbour.bumpAwayFrom(conn); } else { @@ -1451,10 +1417,8 @@ export class BlockSvg } /** - * Schedule snapping to grid and bumping neighbours to occur after a brief - * delay. - * - * @internal + * Snap to grid, and then bump neighbouring blocks away at the end of the next + * render. */ scheduleSnapAndBump() { this.snapToGrid(); diff --git a/core/field.ts b/core/field.ts index 7522cde93..4ee4b1763 100644 --- a/core/field.ts +++ b/core/field.ts @@ -1062,7 +1062,6 @@ export abstract class Field this.isDirty_ = true; if (this.sourceBlock_ && this.sourceBlock_.rendered) { (this.sourceBlock_ as BlockSvg).queueRender(); - (this.sourceBlock_ as BlockSvg).bumpNeighbours(); } } diff --git a/core/gesture.ts b/core/gesture.ts index 4b85c4f6f..2aed48d79 100644 --- a/core/gesture.ts +++ b/core/gesture.ts @@ -125,6 +125,9 @@ export class Gesture { */ private workspaceDragger: WorkspaceDragger | null = null; + /** Whether the gesture is dragging or not. */ + private dragging: boolean = false; + /** The flyout a gesture started in, if any. */ private flyout: IFlyout | null = null; @@ -369,6 +372,7 @@ export class Gesture { : this.startWorkspace_ && this.startWorkspace_.isDraggable(); if (!wsMovable) return; + this.dragging = true; this.workspaceDragger = new WorkspaceDragger(this.startWorkspace_); this.workspaceDragger.startDrag(); @@ -408,6 +412,7 @@ export class Gesture { true, ); + this.dragging = true; this.blockDragger = new BlockDraggerClass!( this.targetBlock, this.startWorkspace_, @@ -431,6 +436,7 @@ export class Gesture { ); } + this.dragging = true; this.bubbleDragger = new BubbleDragger( this.startBubble, this.startWorkspace_, @@ -963,7 +969,8 @@ export class Gesture { eventUtils.setGroup(true); } const newBlock = this.flyout.createBlock(this.targetBlock); - newBlock.scheduleSnapAndBump(); + newBlock.snapToGrid(); + newBlock.bumpNeighbours(); } } else { if (!this.startWorkspace_) { @@ -1205,9 +1212,7 @@ export class Gesture { * @internal */ isDragging(): boolean { - return ( - !!this.workspaceDragger || !!this.blockDragger || !!this.bubbleDragger - ); + return this.dragging; } /** diff --git a/core/inputs/input.ts b/core/inputs/input.ts index a94102aca..bcf8cab78 100644 --- a/core/inputs/input.ts +++ b/core/inputs/input.ts @@ -124,7 +124,6 @@ export class Input { if (this.sourceBlock.rendered) { (this.sourceBlock as BlockSvg).queueRender(); - this.sourceBlock.bumpNeighbours(); } return index; } @@ -145,7 +144,6 @@ export class Input { this.fieldRow.splice(i, 1); if (this.sourceBlock.rendered) { (this.sourceBlock as BlockSvg).queueRender(); - this.sourceBlock.bumpNeighbours(); } return true; } diff --git a/core/render_management.ts b/core/render_management.ts index 541459860..5aadc563c 100644 --- a/core/render_management.ts +++ b/core/render_management.ts @@ -6,6 +6,7 @@ import {BlockSvg} from './block_svg.js'; import * as userAgent from './utils/useragent.js'; +import * as eventUtils from './events/utils.js'; /** The set of all blocks in need of rendering which don't have parents. */ const rootBlocks = new Set(); @@ -13,6 +14,9 @@ const rootBlocks = new Set(); /** The set of all blocks in need of rendering. */ let dirtyBlocks = new WeakSet(); +/** A map from queued blocks to the event group from when they were queued. */ +let eventGroups = new WeakMap(); + /** * The promise which resolves after the current set of renders is completed. Or * null if there are no queued renders. @@ -100,6 +104,7 @@ function alwaysImmediatelyRender() { */ function queueBlock(block: BlockSvg) { dirtyBlocks.add(block); + eventGroups.set(block, eventUtils.getGroup()); const parent = block.getParent(); if (parent) { queueBlock(parent); @@ -124,9 +129,19 @@ function doRenders() { const blockOrigin = block.getRelativeToSurfaceXY(); block.updateComponentLocations(blockOrigin); } + for (const block of blocks) { + const oldGroup = eventUtils.getGroup(); + const newGroup = eventGroups.get(block); + if (newGroup) eventUtils.setGroup(newGroup); + + block.bumpNeighbours(); + + eventUtils.setGroup(oldGroup); + } rootBlocks.clear(); - dirtyBlocks = new Set(); + dirtyBlocks = new WeakSet(); + eventGroups = new WeakMap(); afterRendersPromise = null; } diff --git a/core/rendered_connection.ts b/core/rendered_connection.ts index e68450883..61cc56731 100644 --- a/core/rendered_connection.ts +++ b/core/rendered_connection.ts @@ -499,6 +499,9 @@ export class RenderedConnection extends Connection { const {parentConnection, childConnection} = this.getParentAndChildConnections(); if (!parentConnection || !childConnection) return; + const existingGroup = eventUtils.getGroup(); + if (!existingGroup) eventUtils.setGroup(true); + const parent = parentConnection.getSourceBlock() as BlockSvg; const child = childConnection.getSourceBlock() as BlockSvg; super.disconnectInternal(setParent); @@ -508,6 +511,8 @@ export class RenderedConnection extends Connection { child.queueRender(); // Reset visibility, since the child is now a top block. child.getSvgRoot().style.display = 'block'; + + eventUtils.setGroup(existingGroup); } /** @@ -579,8 +584,6 @@ export class RenderedConnection extends Connection { ) { const child = this.isSuperior() ? this.targetBlock() : this.sourceBlock_; child!.unplug(); - // Bump away. - this.sourceBlock_.bumpNeighbours(); } } diff --git a/tests/mocha/input_test.js b/tests/mocha/input_test.js index c4ecd7a39..73baf20f6 100644 --- a/tests/mocha/input_test.js +++ b/tests/mocha/input_test.js @@ -27,14 +27,12 @@ suite('Inputs', function () { ); this.renderStub = sinon.stub(this.block, 'queueRender'); - this.bumpNeighboursStub = sinon.stub(this.block, 'bumpNeighbours'); this.dummy = this.block.appendDummyInput('DUMMY'); this.value = this.block.appendValueInput('VALUE'); this.statement = this.block.appendStatementInput('STATEMENT'); this.renderStub.resetHistory(); - this.bumpNeighboursStub.resetHistory(); }); teardown(function () { sharedTestTeardown.call(this); @@ -158,7 +156,6 @@ suite('Inputs', function () { chai.assert.equal(setBlockSpy.getCall(0).args[0], this.block); sinon.assert.calledOnce(initSpy); sinon.assert.calledOnce(this.renderStub); - sinon.assert.calledOnce(this.bumpNeighboursStub); setBlockSpy.restore(); initSpy.restore(); @@ -177,7 +174,6 @@ suite('Inputs', function () { chai.assert.equal(setBlockSpy.getCall(0).args[0], this.block); sinon.assert.calledOnce(initModelSpy); sinon.assert.notCalled(this.renderStub); - sinon.assert.notCalled(this.bumpNeighboursStub); setBlockSpy.restore(); initModelSpy.restore(); @@ -196,12 +192,10 @@ suite('Inputs', function () { this.dummy.appendField(field, 'FIELD'); this.renderStub.resetHistory(); - this.bumpNeighboursStub.resetHistory(); this.dummy.removeField('FIELD'); sinon.assert.calledOnce(disposeSpy); sinon.assert.calledOnce(this.renderStub); - sinon.assert.calledOnce(this.bumpNeighboursStub); }); test('Headless', function () { const field = new Blockly.FieldLabel('field'); @@ -209,14 +203,12 @@ suite('Inputs', function () { this.dummy.appendField(field, 'FIELD'); this.renderStub.resetHistory(); - this.bumpNeighboursStub.resetHistory(); this.block.rendered = false; this.dummy.removeField('FIELD'); sinon.assert.calledOnce(disposeSpy); sinon.assert.notCalled(this.renderStub); - sinon.assert.notCalled(this.bumpNeighboursStub); }); }); suite('Field Ordering/Manipulation', function () { diff --git a/tests/mocha/render_management_test.js b/tests/mocha/render_management_test.js index d5d957df4..971ed1667 100644 --- a/tests/mocha/render_management_test.js +++ b/tests/mocha/render_management_test.js @@ -32,6 +32,7 @@ suite('Render Management', function () { isDisposed: () => false, getRelativeToSurfaceXY: () => ({x: 0, y: 0}), updateComponentLocations: () => {}, + bumpNeighbours: () => {}, workspace: { resizeContents: () => {}, }, From 5db9b5bf1174634d06c0bd8198af09f86b257776 Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Thu, 18 Jan 2024 10:12:57 -0800 Subject: [PATCH 13/92] fix: block initialization (#7777) * fix: reorganize initialization * chore: fix failing tests * fix: tests * chore: format * chore: remove console trace --- blocks/procedures.ts | 4 +--- core/block.ts | 15 +++++++++----- core/block_svg.ts | 13 ++++++------ core/field.ts | 1 + core/inputs/input.ts | 30 ++++++++++++++++++++------- core/render_management.ts | 1 + tests/mocha/render_management_test.js | 1 + 7 files changed, 43 insertions(+), 22 deletions(-) diff --git a/blocks/procedures.ts b/blocks/procedures.ts index 7f4a0ef5c..25fe240b8 100644 --- a/blocks/procedures.ts +++ b/blocks/procedures.ts @@ -921,10 +921,9 @@ const PROCEDURE_CALL_COMMON = { type: 'field_label', text: this.arguments_[i], }) as FieldLabel; - const input = this.appendValueInput('ARG' + i) + this.appendValueInput('ARG' + i) .setAlign(Align.RIGHT) .appendField(newField, 'ARGNAME' + i); - input.init(); } } // Remove deleted inputs. @@ -937,7 +936,6 @@ const PROCEDURE_CALL_COMMON = { if (this.arguments_.length) { if (!this.getField('WITH')) { topRow.appendField(Msg['PROCEDURES_CALL_BEFORE_PARAMS'], 'WITH'); - topRow.init(); } } else { if (this.getField('WITH')) { diff --git a/core/block.ts b/core/block.ts index becc8c91b..ea7b63a7a 100644 --- a/core/block.ts +++ b/core/block.ts @@ -191,6 +191,13 @@ export class Block implements IASTNodeLocation, IDeletable { */ private disposing = false; + /** + * Has this block been fully initialized? E.g. all fields initailized. + * + * @internal + */ + initialized = false; + private readonly xy_: Coordinate; isInFlyout: boolean; isInMutator: boolean; @@ -373,13 +380,11 @@ export class Block implements IASTNodeLocation, IDeletable { * change). */ initModel() { + if (this.initialized) return; for (const input of this.inputList) { - for (const field of input.fieldRow) { - if (field.initModel) { - field.initModel(); - } - } + input.initModel(); } + this.initialized = true; } /** diff --git a/core/block_svg.ts b/core/block_svg.ts index 5dcb1d4bd..d9f61dd6d 100644 --- a/core/block_svg.ts +++ b/core/block_svg.ts @@ -163,6 +163,9 @@ export class BlockSvg */ constructor(workspace: WorkspaceSvg, prototypeName: string, opt_id?: string) { super(workspace, prototypeName, opt_id); + if (!workspace.rendered) { + throw TypeError('Cannot create a rendered block in a headless workspace'); + } this.workspace = workspace; this.svgGroup_ = dom.createSvgElement(Svg.G, {}); @@ -189,10 +192,8 @@ export class BlockSvg * May be called more than once. */ initSvg() { - if (!this.workspace.rendered) { - throw TypeError('Workspace is headless.'); - } - for (let i = 0, input; (input = this.inputList[i]); i++) { + if (this.initialized) return; + for (const input of this.inputList) { input.init(); } for (const icon of this.getIcons()) { @@ -202,7 +203,7 @@ export class BlockSvg this.applyColour(); this.pathObject.updateMovable(this.isMovable()); const svg = this.getSvgRoot(); - if (!this.workspace.options.readOnly && !this.eventsInit_ && svg) { + if (!this.workspace.options.readOnly && svg) { browserEvents.conditionalBind( svg, 'pointerdown', @@ -210,11 +211,11 @@ export class BlockSvg this.onMouseDown_, ); } - this.eventsInit_ = true; if (!svg.parentNode) { this.workspace.getCanvas().appendChild(svg); } + this.initialized = true; } /** diff --git a/core/field.ts b/core/field.ts index 4ee4b1763..0a22797bf 100644 --- a/core/field.ts +++ b/core/field.ts @@ -314,6 +314,7 @@ export abstract class Field this.setTooltip(this.tooltip_); this.bindEvents_(); this.initModel(); + this.applyColour(); } /** diff --git a/core/inputs/input.ts b/core/inputs/input.ts index bcf8cab78..da7cccad5 100644 --- a/core/inputs/input.ts +++ b/core/inputs/input.ts @@ -103,10 +103,7 @@ export class Input { } field.setSourceBlock(this.sourceBlock); - if (this.sourceBlock.rendered) { - field.init(); - field.applyColour(); - } + if (this.sourceBlock.initialized) this.initField(field); field.name = opt_name; field.setVisible(this.isVisible()); @@ -270,11 +267,28 @@ export class Input { /** Initialize the fields on this input. */ init() { - if (!this.sourceBlock.rendered) { - return; // Headless blocks don't need fields initialized. + for (const field of this.fieldRow) { + field.init(); } - for (let i = 0; i < this.fieldRow.length; i++) { - this.fieldRow[i].init(); + } + + /** + * Initializes the fields on this input for a headless block. + * + * @internal + */ + public initModel() { + for (const field of this.fieldRow) { + field.initModel(); + } + } + + /** Initializes the given field. */ + private initField(field: Field) { + if (this.sourceBlock.rendered) { + field.init(); + } else { + field.initModel(); } } diff --git a/core/render_management.ts b/core/render_management.ts index 5aadc563c..c5e3d2af5 100644 --- a/core/render_management.ts +++ b/core/render_management.ts @@ -166,6 +166,7 @@ function shouldRenderRootBlock(block: BlockSvg): boolean { */ function renderBlock(block: BlockSvg) { if (!dirtyBlocks.has(block)) return; + if (!block.initialized) return; for (const child of block.getChildren(false)) { renderBlock(child); } diff --git a/tests/mocha/render_management_test.js b/tests/mocha/render_management_test.js index 971ed1667..9d368ef7b 100644 --- a/tests/mocha/render_management_test.js +++ b/tests/mocha/render_management_test.js @@ -33,6 +33,7 @@ suite('Render Management', function () { getRelativeToSurfaceXY: () => ({x: 0, y: 0}), updateComponentLocations: () => {}, bumpNeighbours: () => {}, + initialized: true, workspace: { resizeContents: () => {}, }, From 016bc7397fe8c98f36d4ba3c5b0060fc77d08754 Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Thu, 25 Jan 2024 15:43:34 -0800 Subject: [PATCH 14/92] fix: export save and load procedure (#7806) --- core/serialization/procedures.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/serialization/procedures.ts b/core/serialization/procedures.ts index 688904b08..55d720604 100644 --- a/core/serialization/procedures.ts +++ b/core/serialization/procedures.ts @@ -70,7 +70,7 @@ interface ParameterModelConstructor { /** * Serializes the given IProcedureModel to JSON. */ -function saveProcedure(proc: IProcedureModel): State { +export function saveProcedure(proc: IProcedureModel): State { const state: State = proc.saveState(); if (!proc.getParameters().length) return state; state.parameters = proc.getParameters().map((param) => param.saveState()); @@ -80,7 +80,7 @@ function saveProcedure(proc: IProcedureModel): State { /** * Deserializes the given procedure model State from JSON. */ -function loadProcedure< +export function loadProcedure< ProcedureModel extends IProcedureModel, ParameterModel extends IParameterModel, >( From abe4cf98f23dcc8a97dca0cae52b60790900af98 Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Wed, 7 Feb 2024 17:30:44 +0000 Subject: [PATCH 15/92] chore: fix v11 branch build (#7836) * chore: fix render management lint * fix: build --- core/render_management.ts | 2 +- scripts/gulpfiles/build_tasks.js | 4 ++-- tests/mocha/render_management_test.js | 2 ++ tests/typescript/src/generators.ts | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/core/render_management.ts b/core/render_management.ts index 6b7e604f4..8a1d9407b 100644 --- a/core/render_management.ts +++ b/core/render_management.ts @@ -16,7 +16,7 @@ const rootBlocks = new Set(); const dirtyBlocks = new WeakSet(); /** A map from queued blocks to the event group from when they were queued. */ -let eventGroups = new WeakMap(); +const eventGroups = new WeakMap(); /** * The promise which resolves after the current set of renders is completed. Or diff --git a/scripts/gulpfiles/build_tasks.js b/scripts/gulpfiles/build_tasks.js index ca7f11073..8775f6918 100644 --- a/scripts/gulpfiles/build_tasks.js +++ b/scripts/gulpfiles/build_tasks.js @@ -208,8 +208,6 @@ const JSCOMP_ERROR = [ 'conformanceViolations', 'const', 'constantProperty', - 'deprecated', - 'deprecatedAnnotations', 'duplicateMessage', 'es5Strict', 'externsValidation', @@ -256,6 +254,8 @@ const JSCOMP_ERROR = [ * it's generally sufficient to remove them from JSCOMP_ERROR. */ const JSCOMP_WARNING = [ + 'deprecated', + 'deprecatedAnnotations', ]; /** diff --git a/tests/mocha/render_management_test.js b/tests/mocha/render_management_test.js index 7e22cfffb..7852a5b0c 100644 --- a/tests/mocha/render_management_test.js +++ b/tests/mocha/render_management_test.js @@ -74,6 +74,8 @@ suite('Render Management', function () { isDisposed: () => false, getRelativeToSurfaceXY: () => ({x: 0, y: 0}), updateComponentLocations: () => {}, + bumpNeighbours: () => {}, + initialized: true, workspace: ws || createMockWorkspace(), }; } diff --git a/tests/typescript/src/generators.ts b/tests/typescript/src/generators.ts index a87d70ee3..fd79a3a00 100644 --- a/tests/typescript/src/generators.ts +++ b/tests/typescript/src/generators.ts @@ -28,4 +28,4 @@ testGenerator.forBlock['test_block'] = function ( return ['a fake code string', Order.ADDITION]; }; -phpGenerator.quote_(); +phpGenerator.quote_('a string'); From ad0c0e36bd0c95fa09c5649adee27acc8cc302e1 Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Wed, 7 Feb 2024 18:00:09 +0000 Subject: [PATCH 16/92] fix: lua generators using None instead of nil (#7837) --- generators/lua/lists.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/generators/lua/lists.ts b/generators/lua/lists.ts index 1af18b955..901ed88a6 100644 --- a/generators/lua/lists.ts +++ b/generators/lua/lists.ts @@ -33,7 +33,7 @@ export function lists_create_with( const elements = new Array(createWithBlock.itemCount_); for (let i = 0; i < createWithBlock.itemCount_; i++) { elements[i] = - generator.valueToCode(createWithBlock, 'ADD' + i, Order.NONE) || 'None'; + generator.valueToCode(createWithBlock, 'ADD' + i, Order.NONE) || 'nil'; } const code = '{' + elements.join(', ') + '}'; return [code, Order.HIGH]; @@ -56,7 +56,7 @@ function ${generator.FUNCTION_NAME_PLACEHOLDER_}(item, count) end `, ); - const element = generator.valueToCode(block, 'ITEM', Order.NONE) || 'None'; + const element = generator.valueToCode(block, 'ITEM', Order.NONE) || 'nil'; const repeatCount = generator.valueToCode(block, 'NUM', Order.NONE) || '0'; const code = functionName + '(' + element + ', ' + repeatCount + ')'; return [code, Order.HIGH]; @@ -258,7 +258,7 @@ export function lists_setIndex(block: Block, generator: LuaGenerator): string { const mode = block.getFieldValue('MODE') || 'SET'; const where = block.getFieldValue('WHERE') || 'FROM_START'; const at = generator.valueToCode(block, 'AT', Order.ADDITIVE) || '1'; - const value = generator.valueToCode(block, 'TO', Order.NONE) || 'None'; + const value = generator.valueToCode(block, 'TO', Order.NONE) || 'Nil'; let code = ''; // If `list` would be evaluated more than once (which is the case for LAST, From 48228e47e33469b7751f5108d7d392ee60103640 Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Fri, 8 Mar 2024 22:24:58 +0000 Subject: [PATCH 17/92] chore: merge develop into v11 to fix CI (#7893) * fix: destroy connection highlight when the connection is disposed (#7830) * fix: fix reference docs toc generation (#7832) * fix: insertion marker previewer finding wrong connection for different conn counts (#7833) * feat: make grid options togglable (#7828) * chore(deps): Bump prettier from 3.1.1 to 3.2.5 (#7831) Bumps [prettier](https://github.com/prettier/prettier) from 3.1.1 to 3.2.5. - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/3.1.1...3.2.5) --- updated-dependencies: - dependency-name: prettier dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix: disposing of connection previewer (#7834) * fix: typo in Click event class docs * chore: rollup of updates from translatewiki for 10.4.0 (#7856) * chore: update metadata for v10.4.0 (#7857) * release: update version number to 10.4.0 * fix: connection previewer disposing too early * Merge pull request #7859 from BeksOmega/fix/previewer-disposing fix: connection previewer disposing too early (cherry picked from commit da3ec253bf4af918d5914c87c62b3f4caa0322be) * release: update version number to 10.4.1 * chore: move connection previewer out of subfolder (#7835) * chore(deps-dev): Bump undici from 5.26.3 to 5.28.3 (#7862) Bumps [undici](https://github.com/nodejs/undici) from 5.26.3 to 5.28.3. - [Release notes](https://github.com/nodejs/undici/releases) - [Commits](https://github.com/nodejs/undici/compare/v5.26.3...v5.28.3) --- updated-dependencies: - dependency-name: undici dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore: update webdriverio to fix chrome endpoint issue (#7875) * fix: insertion marker drag scaling (#7874) * fix: insertion marker drag scaling * chore: added docs for other params * fix: restore respecting snap radius (#7873) --------- Signed-off-by: dependabot[bot] Co-authored-by: Maribeth Bottorff Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Elvis Adomnica Co-authored-by: Rachel Fenichel --- core/block_dragger.ts | 28 ++- core/blockly.ts | 2 +- core/events/events_click.ts | 2 +- core/grid.ts | 39 +++- .../insertion_marker_previewer.ts | 17 +- core/rendered_connection.ts | 1 + core/workspace_svg.ts | 1 - msg/json/ar.json | 2 + msg/json/be-tarask.json | 8 + msg/json/bn.json | 10 +- msg/json/da.json | 6 +- msg/json/el.json | 2 +- msg/json/he.json | 2 +- msg/json/id.json | 13 +- msg/json/inh.json | 6 +- msg/json/ja.json | 1 + msg/json/nl.json | 6 +- msg/json/pt.json | 1 + msg/json/qqq.json | 13 +- msg/json/ta.json | 5 +- msg/json/tl.json | 3 +- msg/json/ur.json | 5 +- msg/json/yue.json | 19 -- msg/json/zh-hant.json | 28 +-- package-lock.json | 174 ++++++++++++------ package.json | 4 +- scripts/gulpfiles/docs_tasks.js | 6 +- tests/scripts/check_metadata.sh | 10 +- 28 files changed, 265 insertions(+), 149 deletions(-) rename core/{connection_previewers => }/insertion_marker_previewer.ts (92%) delete mode 100644 msg/json/yue.json diff --git a/core/block_dragger.ts b/core/block_dragger.ts index ada0559ba..8206200a4 100644 --- a/core/block_dragger.ts +++ b/core/block_dragger.ts @@ -193,6 +193,11 @@ export class BlockDragger implements IBlockDragger { this.updateConnectionPreview(block, delta); } + /** + * @param draggingBlock The block being dragged. + * @param dragDelta How far the pointer has moved from the position + * at the start of the drag, in pixel units. + */ private moveBlock(draggingBlock: BlockSvg, dragDelta: Coordinate) { const delta = this.pixelsToWorkspaceUnits_(dragDelta); const newLoc = Coordinate.sum(this.startXY_, delta); @@ -212,6 +217,11 @@ export class BlockDragger implements IBlockDragger { /** * Returns true if we would delete the block if it was dropped at this time, * false otherwise. + * + * @param e The most recent move event. + * @param draggingBlock The block being dragged. + * @param delta How far the pointer has moved from the position + * at the start of the drag, in pixel units. */ private wouldDeleteBlock( e: PointerEvent, @@ -234,7 +244,16 @@ export class BlockDragger implements IBlockDragger { ); } - private updateConnectionPreview(draggingBlock: BlockSvg, delta: Coordinate) { + /** + * @param draggingBlock The block being dragged. + * @param dragDelta How far the pointer has moved from the position + * at the start of the drag, in pixel units. + */ + private updateConnectionPreview( + draggingBlock: BlockSvg, + dragDelta: Coordinate, + ) { + const delta = this.pixelsToWorkspaceUnits_(dragDelta); const currCandidate = this.connectionCandidate; const newCandidate = this.getConnectionCandidate(draggingBlock, delta); if (!newCandidate) { @@ -322,7 +341,9 @@ export class BlockDragger implements IBlockDragger { delta: Coordinate, ): ConnectionCandidate | null { const localConns = this.getLocalConnections(draggingBlock); - let radius = config.snapRadius; + let radius = this.connectionCandidate + ? config.connectingSnapRadius + : config.snapRadius; let candidate = null; for (const conn of localConns) { @@ -405,6 +426,9 @@ export class BlockDragger implements IBlockDragger { ); } } + // Must dispose after `updateBlockAfterMove_` is called to not break the + // dynamic connections plugin. + this.connectionPreviewer.dispose(); this.workspace_.setResizesEnabled(true); eventUtils.setGroup(false); diff --git a/core/blockly.ts b/core/blockly.ts index c7338c37f..2a5dba653 100644 --- a/core/blockly.ts +++ b/core/blockly.ts @@ -125,7 +125,7 @@ import {inject} from './inject.js'; import {Input} from './inputs/input.js'; import * as inputs from './inputs.js'; import {InsertionMarkerManager} from './insertion_marker_manager.js'; -import {InsertionMarkerPreviewer} from './connection_previewers/insertion_marker_previewer.js'; +import {InsertionMarkerPreviewer} from './insertion_marker_previewer.js'; import {IASTNodeLocation} from './interfaces/i_ast_node_location.js'; import {IASTNodeLocationSvg} from './interfaces/i_ast_node_location_svg.js'; import {IASTNodeLocationWithBlock} from './interfaces/i_ast_node_location_with_block.js'; diff --git a/core/events/events_click.ts b/core/events/events_click.ts index 4f2c0c53e..1b1556069 100644 --- a/core/events/events_click.ts +++ b/core/events/events_click.ts @@ -20,7 +20,7 @@ import * as eventUtils from './utils.js'; import {Workspace} from '../workspace.js'; /** - * Notifies listeners that ome blockly element was clicked. + * Notifies listeners that some blockly element was clicked. */ export class Click extends UiBase { /** The ID of the block that was clicked, if a block was clicked. */ diff --git a/core/grid.ts b/core/grid.ts index 952ed018a..28e460fa0 100644 --- a/core/grid.ts +++ b/core/grid.ts @@ -20,11 +20,12 @@ import {GridOptions} from './options.js'; * Class for a workspace's grid. */ export class Grid { - private readonly spacing: number; - private readonly length: number; + private spacing: number; + private length: number; + private scale: number = 1; private readonly line1: SVGElement; private readonly line2: SVGElement; - private readonly snapToGrid: boolean; + private snapToGrid: boolean; /** * @param pattern The grid's SVG pattern, created during injection. @@ -52,6 +53,37 @@ export class Grid { this.snapToGrid = options['snap'] ?? false; } + /** + * Sets the spacing between the centers of the grid lines. + * + * This does not trigger snapping to the newly spaced grid. If you want to + * snap blocks to the grid programmatically that needs to be triggered + * on individual top-level blocks. The next time a block is dragged and + * dropped it will snap to the grid if snapping to the grid is enabled. + */ + setSpacing(spacing: number) { + this.spacing = spacing; + this.update(this.scale); + } + + /** Sets the length of the grid lines. */ + setLength(length: number) { + this.length = length; + this.update(this.scale); + } + + /** + * Sets whether blocks should snap to the grid or not. + * + * Setting this to true does not trigger snapping. If you want to snap blocks + * to the grid programmatically that needs to be triggered on individual + * top-level blocks. The next time a block is dragged and dropped it will + * snap to the grid. + */ + setSnapToGrid(snap: boolean) { + this.snapToGrid = snap; + } + /** * Whether blocks should snap to the grid, based on the initial configuration. * @@ -90,6 +122,7 @@ export class Grid { * @internal */ update(scale: number) { + this.scale = scale; const safeSpacing = this.spacing * scale; this.pattern.setAttribute('width', `${safeSpacing}`); diff --git a/core/connection_previewers/insertion_marker_previewer.ts b/core/insertion_marker_previewer.ts similarity index 92% rename from core/connection_previewers/insertion_marker_previewer.ts rename to core/insertion_marker_previewer.ts index ef77b6462..88c63a65c 100644 --- a/core/connection_previewers/insertion_marker_previewer.ts +++ b/core/insertion_marker_previewer.ts @@ -4,14 +4,14 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {BlockSvg} from '../block_svg.js'; -import {IConnectionPreviewer} from '../interfaces/i_connection_previewer.js'; -import {RenderedConnection} from '../rendered_connection.js'; -import {WorkspaceSvg} from '../workspace_svg.js'; -import * as eventUtils from '../events/utils.js'; -import * as renderManagement from '../render_management.js'; -import * as registry from '../registry.js'; -import * as blocks from '../serialization/blocks.js'; +import {BlockSvg} from './block_svg.js'; +import {IConnectionPreviewer} from './interfaces/i_connection_previewer.js'; +import {RenderedConnection} from './rendered_connection.js'; +import {WorkspaceSvg} from './workspace_svg.js'; +import * as blocks from './serialization/blocks.js'; +import * as eventUtils from './events/utils.js'; +import * as renderManagement from './render_management.js'; +import * as registry from './registry.js'; /** * An error message to throw if the block created by createMarkerBlock_ is @@ -175,6 +175,7 @@ export class InsertionMarkerPreviewer implements IConnectionPreviewer { ) { const origConns = orig.getConnections_(true); const markerConns = marker.getConnections_(true); + if (origConns.length !== markerConns.length) return null; for (let i = 0; i < origConns.length; i++) { if (origConns[i] === origConn) { return markerConns[i]; diff --git a/core/rendered_connection.ts b/core/rendered_connection.ts index 6f0d24188..5aa6f7f75 100644 --- a/core/rendered_connection.ts +++ b/core/rendered_connection.ts @@ -79,6 +79,7 @@ export class RenderedConnection extends Connection { if (this.trackedState === RenderedConnection.TrackedState.TRACKED) { this.db.removeConnection(this, this.y); } + this.sourceBlock_.pathObject.removeConnectionHighlight?.(this); } /** diff --git a/core/workspace_svg.ts b/core/workspace_svg.ts index 68912f3f0..f608311d1 100644 --- a/core/workspace_svg.ts +++ b/core/workspace_svg.ts @@ -2353,7 +2353,6 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * Get the grid object for this workspace, or null if there is none. * * @returns The grid object for this workspace. - * @internal */ getGrid(): Grid | null { return this.grid; diff --git a/msg/json/ar.json b/msg/json/ar.json index 59a3d70ef..2d382ddcf 100644 --- a/msg/json/ar.json +++ b/msg/json/ar.json @@ -9,6 +9,7 @@ "Mido", "Moud hosny", "MuratTheTurkish", + "NEHAOUA", "Samir", "Test Create account", "ديفيد", @@ -49,6 +50,7 @@ "NEW_VARIABLE_TITLE": "اسم المتغير الجديد:", "VARIABLE_ALREADY_EXISTS": "المتغير '%1' موجود بالفعل", "VARIABLE_ALREADY_EXISTS_FOR_ANOTHER_TYPE": "متغير بأسم '%1' معرف من نوع اخر : '%2'.", + "VARIABLE_ALREADY_EXISTS_FOR_A_PARAMETER": "المتغير المسمى '%1' موجود بالفعل كمعلمة في الإجراء '%2'.", "DELETE_VARIABLE_CONFIRMATION": "حذف%1 1 استخدامات المتغير '%2'؟", "CANNOT_DELETE_VARIABLE_PROCEDURE": "لايمكن حذف متغير \"%1\" بسبب انه جزء من الدالة \"%2\"", "DELETE_VARIABLE": "حذف المتغير %1", diff --git a/msg/json/be-tarask.json b/msg/json/be-tarask.json index 26db3ce69..6d5e0ff19 100644 --- a/msg/json/be-tarask.json +++ b/msg/json/be-tarask.json @@ -40,6 +40,7 @@ "NEW_VARIABLE_TITLE": "Імя новай зьменнай:", "VARIABLE_ALREADY_EXISTS": "Зьменная з назвай «%1» ужо існуе.", "VARIABLE_ALREADY_EXISTS_FOR_ANOTHER_TYPE": "Зьменная з назвай «%1» ужо існуе зь іншым тыпам: «%2».", + "VARIABLE_ALREADY_EXISTS_FOR_A_PARAMETER": "Зьменная з назвай '%1' ужо існуе як парамэтар працэдуры '%2'.", "DELETE_VARIABLE_CONFIRMATION": "Выдаліць %1 выкарыстаньняў зьменнай «%2»?", "CANNOT_DELETE_VARIABLE_PROCEDURE": "Немагчыма выдаліць зьменную «%1», таму што яна зьяўляецца часткай вызначэньня функцыі «%2»", "DELETE_VARIABLE": "Выдаліць зьменную «%1»", @@ -107,6 +108,12 @@ "LOGIC_TERNARY_TOOLTIP": "Праверыць умову ў 'тэст'. Калі ўмова праўдзівая, будзе вернутае значэньне «калі ісьціна»; інакш будзе вернутае «калі хлусьня».", "MATH_NUMBER_HELPURL": "https://be-x-old.wikipedia.org/wiki/%D0%9B%D1%96%D0%BA", "MATH_NUMBER_TOOLTIP": "Лік.", + "MATH_TRIG_SIN": "sin", + "MATH_TRIG_COS": "cos", + "MATH_TRIG_TAN": "tan", + "MATH_TRIG_ASIN": "asin", + "MATH_TRIG_ACOS": "acos", + "MATH_TRIG_ATAN": "atan", "MATH_ARITHMETIC_HELPURL": "https://be-x-old.wikipedia.org/wiki/%D0%90%D1%80%D1%8B%D1%82%D0%BC%D1%8D%D1%82%D1%8B%D0%BA%D0%B0", "MATH_ARITHMETIC_TOOLTIP_ADD": "Вяртае суму двух лікаў.", "MATH_ARITHMETIC_TOOLTIP_MINUS": "Вяртае рознасьць двух лікаў.", @@ -243,6 +250,7 @@ "LISTS_GET_INDEX_GET": "атрымаць", "LISTS_GET_INDEX_GET_REMOVE": "атрымаць і выдаліць", "LISTS_GET_INDEX_REMOVE": "выдаліць", + "LISTS_GET_INDEX_FROM_START": "№", "LISTS_GET_INDEX_FROM_END": "№ з канца", "LISTS_GET_INDEX_FIRST": "першы", "LISTS_GET_INDEX_LAST": "апошні", diff --git a/msg/json/bn.json b/msg/json/bn.json index 3b5491165..115b415ab 100644 --- a/msg/json/bn.json +++ b/msg/json/bn.json @@ -67,7 +67,7 @@ "CONTROLS_FOR_TOOLTIP": "চলক %1 প্রস্তুত করুন, শুরু থেকে শেষ পর্যন্ত সংখ্যা গ্রহন করতে যা নির্দেশিত বিরতি গণনা করছে এবং নির্দেশিত ব্লক সমূহ সম্পন্ন করতে।", "CONTROLS_FOR_TITLE": "গণনা কর %1 %4 দিয়ে %2 থেকে %3", "CONTROLS_FOREACH_TITLE": "প্রত্যেকটি পদের জন্য %1 তালিকার মধ্যে %2", - "CONTROLS_FOREACH_TOOLTIP": "কোন তালিকায় প্রতিবারের জন্য, আইটেমের সাথে চলক '%1' বসান এবং কিছু বিবরণ দিন।", + "CONTROLS_FOREACH_TOOLTIP": "কোনো তালিকায় প্রতিবারের জন্য, আইটেমের সাথে চলক '%1' বসান এবং কিছু বিবরণ দিন।", "CONTROLS_FLOW_STATEMENTS_OPERATOR_BREAK": "প্রবেশপথ থেকে চলে অাসুন", "CONTROLS_FLOW_STATEMENTS_OPERATOR_CONTINUE": "পুনরাবৃত্তি চালিয়ে যান পরবর্তী প্রবেশপথে", "CONTROLS_FLOW_STATEMENTS_TOOLTIP_BREAK": "সংশ্লিষ্ট প্রবেশপথ থেকে চলে অাসুন", @@ -88,7 +88,7 @@ "LOGIC_COMPARE_TOOLTIP_GTE": "পাঠাবে সত্য যদি প্রথম ইনপুট দ্বিতীয় ইনপুট থেকে বড় অথবা সমান হয়।", "LOGIC_OPERATION_TOOLTIP_AND": "পাঠাবে সত্য যদি উভয় ইনপুটই সত্য হয়।", "LOGIC_OPERATION_AND": "এবং", - "LOGIC_OPERATION_TOOLTIP_OR": "পাঠাবে সত্য যদি অন্ততপক্ষে যেকোন একটি ইনপুট সত্য হয়।", + "LOGIC_OPERATION_TOOLTIP_OR": "পাঠাবে সত্য যদি অন্ততপক্ষে যেকোনো একটি ইনপুট সত্য হয়।", "LOGIC_OPERATION_OR": "অথবা", "LOGIC_NEGATE_TITLE": "%1 নয়", "LOGIC_NEGATE_TOOLTIP": "পাঠাবে সত্য যদি ইনপুট মিথ্যা হয়। পাঠাবে মিথ্যা যদি ইনপুট সত্য হয়।", @@ -145,8 +145,8 @@ "TEXT_PRINT_TITLE": "%1 মুদ্রণ করুন", "TEXT_REVERSE_MESSAGE0": "%1 উল্টান", "LISTS_CREATE_EMPTY_TITLE": "খালি তালিকা তৈরি করুন", - "LISTS_CREATE_EMPTY_TOOLTIP": "পাঠাবে একটি তালিকা, দের্ঘ্য হবে ০, কোন উপাত্ত থাকবে না", - "LISTS_CREATE_WITH_TOOLTIP": "যেকোন সংখ্যক পদ নিয়ে একটি তালিকা তৈরি করুন।", + "LISTS_CREATE_EMPTY_TOOLTIP": "পাঠাবে একটি তালিকা, দের্ঘ্য হবে ০, কোনো উপাত্ত থাকবে না", + "LISTS_CREATE_WITH_TOOLTIP": "যেকোনো সংখ্যক পদ নিয়ে একটি তালিকা তৈরি করুন।", "LISTS_CREATE_WITH_CONTAINER_TITLE_ADD": "তালিকা", "LISTS_CREATE_WITH_ITEM_TOOLTIP": "তালিকায় একটি পদ যোগ করুন।", "LISTS_LENGTH_TITLE": "%1-এর দৈর্ঘ্য", @@ -165,7 +165,7 @@ "LISTS_GET_INDEX_RANDOM": "এলোমেলো", "LISTS_GET_INDEX_TOOLTIP_GET_FIRST": "তালিকার প্রথম পদটি পাঠাবে।", "LISTS_GET_INDEX_TOOLTIP_GET_LAST": "তালিকার শেষ পদটি পাঠাবে।", - "LISTS_GET_INDEX_TOOLTIP_GET_RANDOM": "এলোমেলোভাবে তালিকার যেকোন একটি পদ পাঠাবে।", + "LISTS_GET_INDEX_TOOLTIP_GET_RANDOM": "এলোমেলোভাবে তালিকার যেকোনো একটি পদ পাঠাবে।", "LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_FIRST": "অপসারণ করুন এবং তালিকার প্রথম পদটি পাঠান।", "LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_LAST": "অপসারণ করুন এবং তালিকার শেষ পদটি পাঠান।", "LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_RANDOM": "অপসারণ করুন এবং তালিকার এলোমেলো একটি পদ পাঠান।", diff --git a/msg/json/da.json b/msg/json/da.json index 2795a69c6..4c9febebc 100644 --- a/msg/json/da.json +++ b/msg/json/da.json @@ -19,8 +19,8 @@ "ADD_COMMENT": "Tilføj Kommentar", "REMOVE_COMMENT": "Fjern Kommentar", "DUPLICATE_COMMENT": "Duplikér Kommentar", - "EXTERNAL_INPUTS": "Udvendige inputs", - "INLINE_INPUTS": "Indlejrede inputs", + "EXTERNAL_INPUTS": "Eksterne inputs", + "INLINE_INPUTS": "Interne inputs", "DELETE_BLOCK": "Slet blok", "DELETE_X_BLOCKS": "Slet %1 blokke", "DELETE_ALL_BLOCKS": "Slet alle %1 blokke?", @@ -45,6 +45,7 @@ "NEW_VARIABLE_TITLE": "Navn til den nye variabel:", "VARIABLE_ALREADY_EXISTS": "En variabel med navnet »%1« findes allerede.", "VARIABLE_ALREADY_EXISTS_FOR_ANOTHER_TYPE": "En variabel med navnet »%1« findes allerede for en anden type: »%2«.", + "VARIABLE_ALREADY_EXISTS_FOR_A_PARAMETER": "En variabel med navnet '%1' findes allerede som en parameter i proceduren '%2'.", "DELETE_VARIABLE_CONFIRMATION": "Slet %1's brug af variablen »%2«?", "CANNOT_DELETE_VARIABLE_PROCEDURE": "Kan ikke slette variablen »%1« da den er en del af definitionen af funktionen »%2«", "DELETE_VARIABLE": "Slet variablen »%1«", @@ -188,6 +189,7 @@ "MATH_RANDOM_FLOAT_TITLE_RANDOM": "tilfældigt decimaltal (mellem 0 og 1)", "MATH_RANDOM_FLOAT_TOOLTIP": "Returner et tilfældigt decimaltal mellem 0,0 (inklusiv) og 1,0 (eksklusiv).", "MATH_ATAN2_TITLE": "atan2 af X:%1 Y:%2", + "MATH_ATAN2_TOOLTIP": "Returner arctangensen for punktet (X, Y) i grader fra -180 til 180.", "TEXT_TEXT_HELPURL": "https://da.wikipedia.org/wiki/Tekststreng", "TEXT_TEXT_TOOLTIP": "En bogstav, et ord eller en linje med tekst.", "TEXT_JOIN_TITLE_CREATEWITH": "lav en tekst med", diff --git a/msg/json/el.json b/msg/json/el.json index abe7a8141..6d6638abf 100644 --- a/msg/json/el.json +++ b/msg/json/el.json @@ -200,7 +200,7 @@ "TEXT_TEXT_HELPURL": "https://el.wikipedia.org/wiki/%CE%A3%CF%85%CE%BC%CE%B2%CE%BF%CE%BB%CE%BF%CF%83%CE%B5%CE%B9%CF%81%CE%AC", "TEXT_TEXT_TOOLTIP": "Ένα γράμμα, μια λέξη ή μια γραμμή κειμένου.", "TEXT_JOIN_TITLE_CREATEWITH": "δημιούργησε κείμενο με", - "TEXT_JOIN_TOOLTIP": "Δημιουργεί ένα κομμάτι κειμένου ενώνοντας έναν απεριόριστο αριθμό αντικειμένων.", + "TEXT_JOIN_TOOLTIP": "Δημιουργεί ένα κομμάτι κειμένου ενώνοντας έναν απεριόριστο αριθμό αντικειμένων.", "TEXT_CREATE_JOIN_TITLE_JOIN": "ένωσε", "TEXT_CREATE_JOIN_TOOLTIP": "Προσθέτει, αφαιρεί ή αναδιατάσσει τους τομείς για να αναδιαμορφώσει αυτό το μπλοκ κειμένου.", "TEXT_CREATE_JOIN_ITEM_TOOLTIP": "Προσθέτει ένα στοιχείο στο κείμενο.", diff --git a/msg/json/he.json b/msg/json/he.json index 0d38d0766..c5db9108e 100644 --- a/msg/json/he.json +++ b/msg/json/he.json @@ -56,7 +56,7 @@ "DELETE_VARIABLE_CONFIRMATION": "למחוק %1 שימושים במשתנה ‚%2’?", "CANNOT_DELETE_VARIABLE_PROCEDURE": "אי אפשר למחוק את המשתנה \"%1\", מכיוון שהגדרת הפונקציה \"%2\" משתמשת בו.", "DELETE_VARIABLE": "מחק את משתנה ה'%1'", - "COLOUR_PICKER_HELPURL": "https://he.wikipedia.org/wiki/%D7%A6%D7%91%D7%A2", + "COLOUR_PICKER_HELPURL": "https://he.wikipedia.org/wiki/צבע", "COLOUR_PICKER_TOOLTIP": "בחר צבע מן הצבעים.", "COLOUR_RANDOM_TITLE": "צבע אקראי", "COLOUR_RANDOM_TOOLTIP": "בחר צבא אקראי.", diff --git a/msg/json/id.json b/msg/json/id.json index 9346e594a..bea468f98 100644 --- a/msg/json/id.json +++ b/msg/json/id.json @@ -4,6 +4,7 @@ "Adisetiawan", "Akmaie Ajam", "Arifin.wijaya", + "Daud I.F. Argana", "Kasimtan", "Kenrick95", "Marwan Mohamad", @@ -16,16 +17,16 @@ "VARIABLES_DEFAULT_NAME": "item", "UNNAMED_KEY": "tanpa nama", "TODAY": "Hari ini", - "DUPLICATE_BLOCK": "Duplikat", + "DUPLICATE_BLOCK": "Gandakan", "ADD_COMMENT": "Tambahkan Komentar", "REMOVE_COMMENT": "Hapus Komentar", - "DUPLICATE_COMMENT": "Duplikat Komentar", + "DUPLICATE_COMMENT": "Gandakan Komentar", "EXTERNAL_INPUTS": "Input Eksternal", "INLINE_INPUTS": "Input Inline", "DELETE_BLOCK": "Hapus Blok", "DELETE_X_BLOCKS": "Hapus %1 Blok", "DELETE_ALL_BLOCKS": "Hapus semua %1 blok?", - "CLEAN_UP": "Bersihkan Blok", + "CLEAN_UP": "Rapikan Blok", "COLLAPSE_BLOCK": "Ciutkan Blok", "COLLAPSE_ALL": "Ciutkan Blok", "EXPAND_BLOCK": "Kembangkan Blok", @@ -45,7 +46,7 @@ "NEW_VARIABLE_TYPE_TITLE": "Tipe variabel baru:", "NEW_VARIABLE_TITLE": "Nama variabel baru:", "VARIABLE_ALREADY_EXISTS": "Sebuah variabel dengan nama '%1' sudah ada.", - "VARIABLE_ALREADY_EXISTS_FOR_ANOTHER_TYPE": "Variabel yg bernama '%1' sudah ada untuk tipe lain: '%2'.", + "VARIABLE_ALREADY_EXISTS_FOR_ANOTHER_TYPE": "Variabel dengan nama '%1' sudah ada dengan tipe lain: '%2'.", "VARIABLE_ALREADY_EXISTS_FOR_A_PARAMETER": "Variabel bernama '%1' sudah ada sebagai parameter dalam prosedur '%2'.", "DELETE_VARIABLE_CONFIRMATION": "Hapus %1 yang digunakan pada variabel '%2'?", "CANNOT_DELETE_VARIABLE_PROCEDURE": "Tidak bisa menghapus variabel '%1' karena variabel ini bagian dari sebuah definisi dari fungsi '%2'", @@ -57,11 +58,11 @@ "COLOUR_RGB_RED": "merah", "COLOUR_RGB_GREEN": "hijau", "COLOUR_RGB_BLUE": "biru", - "COLOUR_RGB_TOOLTIP": "Buatlah warna dengan jumlah yang ditentukan dari merah, hijau dan biru. Semua nilai harus antarai 0 sampai 100.", + "COLOUR_RGB_TOOLTIP": "Buatlah warna dengan jumlah yang ditentukan dari merah, hijau dan biru. Semua nilai harus di antara 0 sampai 100.", "COLOUR_BLEND_TITLE": "campur", "COLOUR_BLEND_COLOUR1": "warna 1", "COLOUR_BLEND_COLOUR2": "warna 2", - "COLOUR_BLEND_RATIO": "rasio", + "COLOUR_BLEND_RATIO": "perbandingan", "COLOUR_BLEND_TOOLTIP": "Campur dua warna secara bersamaan dengan perbandingan (0.0 - 1.0).", "CONTROLS_REPEAT_TITLE": "ulangi %1 kali", "CONTROLS_REPEAT_INPUT_DO": "kerjakan", diff --git a/msg/json/inh.json b/msg/json/inh.json index c121fe38a..c9627b333 100644 --- a/msg/json/inh.json +++ b/msg/json/inh.json @@ -10,14 +10,14 @@ "VARIABLES_DEFAULT_NAME": "элемент", "UNNAMED_KEY": "цӀи яц", "TODAY": "Тахан", - "DUPLICATE_BLOCK": "Кеп яккха", + "DUPLICATE_BLOCK": "Шолхадаккха", "ADD_COMMENT": "ТӀатоха алар (комментари)", "REMOVE_COMMENT": "ДӀадаккха алар (комментари)", "DUPLICATE_COMMENT": "Комментарий шолхаяккха", "EXTERNAL_INPUTS": "Арахьара юкъеоттадаьраш", "INLINE_INPUTS": "Чухьнахьара юкъеоттадаьраш", - "DELETE_BLOCK": "ДӀаяккха блок", - "DELETE_X_BLOCKS": "ДӀаяккха %1 блокаш", + "DELETE_BLOCK": "Блок дӀаяккха", + "DELETE_X_BLOCKS": "%1 блок дӀаяккха", "DELETE_ALL_BLOCKS": "ДӀаяккха еррига блокаш (%1)?", "CLEAN_UP": "ДӀаяха блокаш", "COLLAPSE_BLOCK": "ДIахьулъе блок", diff --git a/msg/json/ja.json b/msg/json/ja.json index b85adf44b..0cee86da9 100644 --- a/msg/json/ja.json +++ b/msg/json/ja.json @@ -56,6 +56,7 @@ "NEW_VARIABLE_TITLE": "新しい変数の名前:", "VARIABLE_ALREADY_EXISTS": "変数名 '%1' は既に存在しています。", "VARIABLE_ALREADY_EXISTS_FOR_ANOTHER_TYPE": "'%2' 型の '%1' という名前の変数が既に存在します。", + "VARIABLE_ALREADY_EXISTS_FOR_A_PARAMETER": "'%2' 手続きの '%1' という名前の変数が既に存在します。", "DELETE_VARIABLE_CONFIRMATION": "%1か所で使われている変数 '%2' を削除しますか?", "CANNOT_DELETE_VARIABLE_PROCEDURE": "変数 '%1' は関数 '%2' の定義の一部であるため、削除できません", "DELETE_VARIABLE": "変数 '%1' を削除", diff --git a/msg/json/nl.json b/msg/json/nl.json index eb0f4a8a8..8961dc112 100644 --- a/msg/json/nl.json +++ b/msg/json/nl.json @@ -120,7 +120,7 @@ "LOGIC_TERNARY_IF_TRUE": "als waar", "LOGIC_TERNARY_IF_FALSE": "als onwaar", "LOGIC_TERNARY_TOOLTIP": "Test de voorwaarde in \"test\". Als de voorwaarde \"waar\" is, geef de waarde van \"als waar\" terug; geef anders de waarde van \"als onwaar\" terug.", - "MATH_NUMBER_HELPURL": "https://nl.wikipedia.org/wiki/Getal_%28wiskunde%29", + "MATH_NUMBER_HELPURL": "https://nl.wikipedia.org/wiki/Getal_(wiskunde)", "MATH_NUMBER_TOOLTIP": "Een getal.", "MATH_TRIG_SIN": "sin", "MATH_TRIG_COS": "cos", @@ -152,7 +152,7 @@ "MATH_TRIG_TOOLTIP_ACOS": "Geeft de arccosinus van een getal.", "MATH_TRIG_TOOLTIP_ATAN": "Geeft de arctangens van een getal.", "MATH_CONSTANT_HELPURL": "https://nl.wikipedia.org/wiki/Wiskundige_constante", - "MATH_CONSTANT_TOOLTIP": "Geeft een van de vaak voorkomende constante waardes: π (3.141…), e (2.718…), φ (1.618…), √2 (1.414…), √½ (0.707…), of ∞ (oneindig).", + "MATH_CONSTANT_TOOLTIP": "Retourneert een van de vaak voorkomende constanten: π (3.141…), e (2.718…), φ (1.618…), √2 (1.414…), √½ (0.707…), of ∞ (oneindig).", "MATH_IS_EVEN": "is even", "MATH_IS_ODD": "is oneven", "MATH_IS_PRIME": "is priemgetal", @@ -197,7 +197,7 @@ "MATH_RANDOM_FLOAT_TOOLTIP": "Geeft een willekeurige fractie tussen 0.0 (inclusief) en 1.0 (exclusief).", "MATH_ATAN2_TITLE": "atan2 van X:%1 Y:%2", "MATH_ATAN2_TOOLTIP": "Geef de boogtangens van punt (X, Y) terug in graden tussen -180 naar 180.", - "TEXT_TEXT_HELPURL": "https://nl.wikipedia.org/wiki/String_%28informatica%29", + "TEXT_TEXT_HELPURL": "https://nl.wikipedia.org/wiki/Tekenreeks", "TEXT_TEXT_TOOLTIP": "Een letter, woord of een regel tekst.", "TEXT_JOIN_TITLE_CREATEWITH": "maak tekst met", "TEXT_JOIN_TOOLTIP": "Maakt een stuk tekst door één of meer items samen te voegen.", diff --git a/msg/json/pt.json b/msg/json/pt.json index 581527b2f..a024b1b30 100644 --- a/msg/json/pt.json +++ b/msg/json/pt.json @@ -53,6 +53,7 @@ "NEW_VARIABLE_TITLE": "Nome da nova variável:", "VARIABLE_ALREADY_EXISTS": "Já existe uma variável com o nome de '%1'.", "VARIABLE_ALREADY_EXISTS_FOR_ANOTHER_TYPE": "Já existe uma variável chamada '%1' para outra do tipo: '%2'.", + "VARIABLE_ALREADY_EXISTS_FOR_A_PARAMETER": "Já existe uma variável chamada '%1' como parâmetro no procedimento '%2'.", "DELETE_VARIABLE_CONFIRMATION": "Eliminar %1 utilizações da variável '%2'?", "CANNOT_DELETE_VARIABLE_PROCEDURE": "Não se pode eliminar a variável '%1' porque faz parte da definição da função '%2'", "DELETE_VARIABLE": "Eliminar a variável '%1'", diff --git a/msg/json/qqq.json b/msg/json/qqq.json index e60ee223e..60509d798 100644 --- a/msg/json/qqq.json +++ b/msg/json/qqq.json @@ -6,6 +6,7 @@ "Espertus", "Liuxinyu970226", "Metalhead64", + "Nike", "Robby", "Shirayuki" ] @@ -52,7 +53,7 @@ "COLOUR_RANDOM_HELPURL": "{{Optional}} url - A link that displays a random colour each time you visit it.", "COLOUR_RANDOM_TITLE": "block text - Title of block that generates a colour at random.", "COLOUR_RANDOM_TOOLTIP": "tooltip - See [https://github.com/google/blockly/wiki/Colour#generating-a-random-colour https://github.com/google/blockly/wiki/Colour#generating-a-random-colour].", - "COLOUR_RGB_HELPURL": "{{Optional}} url - A link for colour codes with percentages (0-100%) for each component, instead of the more common 0-255, which may be more difficult for beginners.", + "COLOUR_RGB_HELPURL": "{{Ignored}} url - A link for colour codes with percentages (0-100%) for each component, instead of the more common 0-255, which may be more difficult for beginners.", "COLOUR_RGB_TITLE": "block text - Title of block for [https://github.com/google/blockly/wiki/Colour#creating-a-colour-from-red-green-and-blue-components https://github.com/google/blockly/wiki/Colour#creating-a-colour-from-red-green-and-blue-components].", "COLOUR_RGB_RED": "block input text - The amount of red (from 0 to 100) to use when [https://github.com/google/blockly/wiki/Colour#creating-a-colour-from-red-green-and-blue-components https://github.com/google/blockly/wiki/Colour#creating-a-colour-from-red-green-and-blue-components].\n{{Identical|Red}}", "COLOUR_RGB_GREEN": "block input text - The amount of green (from 0 to 100) to use when [https://github.com/google/blockly/wiki/Colour#creating-a-colour-from-red-green-and-blue-components https://github.com/google/blockly/wiki/Colour#creating-a-colour-from-red-green-and-blue-components].", @@ -249,7 +250,7 @@ "TEXT_GET_SUBSTRING_END_FROM_START": "dropdown - Indicates that the following number specifies the position (relative to the start position) of the end of the region of text that should be obtained from the preceding piece of text. See [https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text]. [[File:Blockly-get-substring.png]]", "TEXT_GET_SUBSTRING_END_FROM_END": "dropdown - Indicates that the following number specifies the position (relative to the end position) of the end of the region of text that should be obtained from the preceding piece of text. See [https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text]. [[File:Blockly-get-substring.png]]", "TEXT_GET_SUBSTRING_END_LAST": "block text - Indicates that a region ending with the last letter of the preceding piece of text should be extracted. See [https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text]. [[File:Blockly-get-substring.png]]", - "TEXT_GET_SUBSTRING_TAIL": "{{Optional|Supply translation only if your language requires it. Most do not.}} block text - Text that should go after the rightmost block/dropdown when [https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text extracting a region of text]. In most languages, this will be the empty string. [[File:Blockly-get-substring.png]]", + "TEXT_GET_SUBSTRING_TAIL": "{{Optional|Supply translation only if your language requires it. Most do not.}}\nblock text - Text that should go after the rightmost block/dropdown when [https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text extracting a region of text]. In most languages, this will be the empty string. [[File:Blockly-get-substring.png]]", "TEXT_CHANGECASE_HELPURL": "{{Optional}} url - Information about the case of letters (upper-case and lower-case).", "TEXT_CHANGECASE_TOOLTIP": "tooltip - Describes a block to adjust the case of letters. For more information on this block, see [https://github.com/google/blockly/wiki/Text#adjusting-text-case https://github.com/google/blockly/wiki/Text#adjusting-text-case].", "TEXT_CHANGECASE_OPERATOR_UPPERCASE": "block text - Indicates that all of the letters in the following piece of text should be capitalized. If your language does not use case, you may indicate that this is not applicable to your language. For more information on this block, see [https://github.com/google/blockly/wiki/Text#adjusting-text-case https://github.com/google/blockly/wiki/Text#adjusting-text-case].", @@ -308,7 +309,7 @@ "LISTS_GET_INDEX_FIRST": "dropdown - Indicates that the '''first''' item should be [https://github.com/google/blockly/wiki/Lists#getting-a-single-item accessed in a list]. [[File:Blockly-list-get-item.png]]", "LISTS_GET_INDEX_LAST": "dropdown - Indicates that the '''last''' item should be [https://github.com/google/blockly/wiki/Lists#getting-a-single-item accessed in a list]. [[File:Blockly-list-get-item.png]]", "LISTS_GET_INDEX_RANDOM": "dropdown - Indicates that a '''random''' item should be [https://github.com/google/blockly/wiki/Lists#getting-a-single-item accessed in a list]. [[File:Blockly-list-get-item.png]]", - "LISTS_GET_INDEX_TAIL": "{{Optional|Supply translation only if your language requires it. Most do not.}} block text - Text that should go after the rightmost block/dropdown when [https://github.com/google/blockly/wiki/Lists#getting-a-single-item accessing an item from a list]. In most languages, this will be the empty string. [[File:Blockly-list-get-item.png]]", + "LISTS_GET_INDEX_TAIL": "{{Optional|Supply translation only if your language requires it. Most do not.}}\n\nblock text - Text that should go after the rightmost block/dropdown when [https://github.com/google/blockly/wiki/Lists#getting-a-single-item accessing an item from a list]. In most languages, this will be the empty string. [[File:Blockly-list-get-item.png]]", "LISTS_INDEX_FROM_START_TOOLTIP": "tooltip - Indicates the ordinal number that the first item in a list is referenced by. %1 will be replaced by either '#0' or '#1' depending on the indexing mode.", "LISTS_INDEX_FROM_END_TOOLTIP": "tooltip - Indicates the ordinal number that the last item in a list is referenced by. %1 will be replaced by either '#0' or '#1' depending on the indexing mode.", "LISTS_GET_INDEX_TOOLTIP_GET_FROM": "tooltip - See [https://github.com/google/blockly/wiki/Lists#getting-a-single-item https://github.com/google/blockly/wiki/Lists#getting-a-single-item] for more information.", @@ -342,7 +343,7 @@ "LISTS_GET_SUBLIST_END_FROM_START": "dropdown - Indicates that an index relative to the front of the list should be used to specify the end of the range from which to [https://github.com/google/blockly/wiki/Lists#getting-a-sublist get a sublist]. [[File:Blockly-get-sublist.png]]", "LISTS_GET_SUBLIST_END_FROM_END": "dropdown - Indicates that an index relative to the end of the list should be used to specify the end of the range from which to [https://github.com/google/blockly/wiki/Lists#getting-a-sublist get a sublist]. [[File:Blockly-get-sublist.png]]", "LISTS_GET_SUBLIST_END_LAST": "dropdown - Indicates that the '''last''' item in the given list should be [https://github.com/google/blockly/wiki/Lists#getting-a-sublist the end of the selected sublist]. [[File:Blockly-get-sublist.png]]", - "LISTS_GET_SUBLIST_TAIL": "{{Optional|Supply translation only if your language requires it. Most do not.}} block text - This appears in the rightmost position ('tail') of the sublist block, as described at [https://github.com/google/blockly/wiki/Lists#getting-a-sublist https://github.com/google/blockly/wiki/Lists#getting-a-sublist]. In English and most other languages, this is the empty string. [[File:Blockly-get-sublist.png]]", + "LISTS_GET_SUBLIST_TAIL": "{{Optional}}\nblock text - This appears in the rightmost position ('tail') of the sublist block, as described at [https://github.com/google/blockly/wiki/Lists#getting-a-sublist https://github.com/google/blockly/wiki/Lists#getting-a-sublist]. In English and most other languages, this is the empty string. [[File:Blockly-get-sublist.png]]", "LISTS_GET_SUBLIST_TOOLTIP": "tooltip - See [https://github.com/google/blockly/wiki/Lists#getting-a-sublist https://github.com/google/blockly/wiki/Lists#getting-a-sublist] for more information. [[File:Blockly-get-sublist.png]]", "LISTS_SORT_HELPURL": "{{Optional}} url - Information describing sorting a list.", "LISTS_SORT_TITLE": "Sort as type %1 (numeric or alphabetic) in order %2 (ascending or descending) a list of items %3.\n{{Identical|Sort}}", @@ -361,7 +362,7 @@ "LISTS_REVERSE_HELPURL": "{{Optional}} url - Information describing reversing a list.", "LISTS_REVERSE_MESSAGE0": "block text - Title of block that returns a copy of a list (%1) with the order of items reversed.", "LISTS_REVERSE_TOOLTIP": "tooltip - Short description for a block that reverses a copy of a list.", - "ORDINAL_NUMBER_SUFFIX": "{{Optional|Supply translation only if your language requires it. Most do not.}} grammar - Text that follows an ordinal number (a number that indicates position relative to other numbers). In most languages, such text appears before the number, so this should be blank. An exception is Hungarian. See [[Translating:Blockly#Ordinal_numbers]] for more information.", + "ORDINAL_NUMBER_SUFFIX": "{{Optional}}\ngrammar - Text that follows an ordinal number (a number that indicates position relative to other numbers). In most languages, such text appears before the number, so this should be blank. An exception is Hungarian. See [[Translating:Blockly#Ordinal_numbers]] for more information.", "VARIABLES_GET_HELPURL": "{{Optional}} url - Information about ''variables'' in computer programming. Consider using your language's translation of [https://en.wikipedia.org/wiki/Variable_(computer_science) https://en.wikipedia.org/wiki/Variable_(computer_science)], if it exists.", "VARIABLES_GET_TOOLTIP": "tooltip - This gets the value of the named variable without modifying it.", "VARIABLES_GET_CREATE_SET": "context menu - Selecting this creates a block to set (change) the value of this variable. \n\nParameters:\n* %1 - the name of the variable.", @@ -374,7 +375,7 @@ "PROCEDURES_DEFNORETURN_PROCEDURE": "default name - This acts as a placeholder for the name of a function on a function definition block, as shown on [https://blockly-demo.appspot.com/static/apps/code/index.html?lang=en#w7cfju this block]. The user will replace it with the function's name.", "PROCEDURES_BEFORE_PARAMS": "block text - This precedes the list of parameters on a function's definition block. See [https://blockly-demo.appspot.com/static/apps/code/index.html?lang=en#voztpd this sample function with parameters].", "PROCEDURES_CALL_BEFORE_PARAMS": "block text - This precedes the list of parameters on a function's caller block. See [https://blockly-demo.appspot.com/static/apps/code/index.html?lang=en#voztpd this sample function with parameters].", - "PROCEDURES_DEFNORETURN_DO": "{{Optional|Supply translation only if your language requires it. Most do not.}} block text - This appears next to the function's 'body', the blocks that should be run when the function is called, as shown in [https://blockly-demo.appspot.com/static/apps/code/index.html?lang=en#voztpd this sample function definition].", + "PROCEDURES_DEFNORETURN_DO": "{{Optional}}\nblock text - This appears next to the function's 'body', the blocks that should be run when the function is called, as shown in [https://blockly-demo.appspot.com/static/apps/code/index.html?lang=en#voztpd this sample function definition].", "PROCEDURES_DEFNORETURN_TOOLTIP": "tooltip", "PROCEDURES_DEFNORETURN_COMMENT": "Placeholder text that the user is encouraged to replace with a description of what their function does.", "PROCEDURES_DEFRETURN_HELPURL": "{{Optional}} url - Information about defining [https://en.wikipedia.org/wiki/Subroutine functions] that have return values.", diff --git a/msg/json/ta.json b/msg/json/ta.json index 3af458574..9eb44de12 100644 --- a/msg/json/ta.json +++ b/msg/json/ta.json @@ -4,6 +4,7 @@ "Aswn", "ElangoRamanujam", "Ezhillang", + "Fahimrazick", "Karuthan", "Mahir78", "Thangamani-arun" @@ -222,8 +223,8 @@ "LISTS_INDEX_OF_LAST": "உரையில் கடைசி தோற்ற இடத்தை காட்டு", "LISTS_INDEX_OF_TOOLTIP": "பட்டியலில் மதிப்பின் முதல், கடைசி தோற்ற இடத்தை பின்கொடு. காணாவிட்டால் %1 பின்கொடு.", "LISTS_GET_INDEX_GET": "எடு", - "LISTS_GET_INDEX_GET_REMOVE": "பெற்று நீக்குக", - "LISTS_GET_INDEX_REMOVE": "நீக்குக", + "LISTS_GET_INDEX_GET_REMOVE": "பெற்று நீக்கு", + "LISTS_GET_INDEX_REMOVE": "அகற்று", "LISTS_GET_INDEX_FROM_END": "கடைசியில் இருந்து #", "LISTS_GET_INDEX_FIRST": "முதல்", "LISTS_GET_INDEX_LAST": "கடைசி", diff --git a/msg/json/tl.json b/msg/json/tl.json index 4125784f2..fad9ab9f9 100644 --- a/msg/json/tl.json +++ b/msg/json/tl.json @@ -47,7 +47,7 @@ "CONTROLS_FLOW_STATEMENTS_TOOLTIP_BREAK": "Hatiin ang nilalaman ng loop.", "CONTROLS_FLOW_STATEMENTS_TOOLTIP_CONTINUE": "Laktawan ang natitirang bahagi ng loop, at magpatuloy sa susunod na pag-ulit.", "CONTROLS_FLOW_STATEMENTS_WARNING": "Babala: Ang block ito ay maaari lamang magamit sa loob ng loop.", - "CONTROLS_IF_TOOLTIP_1": "kung ang value ay true, gagawin ang do statements.", + "CONTROLS_IF_TOOLTIP_1": "kung ang value ay true, gagawin ang do statements.", "CONTROLS_IF_TOOLTIP_2": "Kung ang value ay true, gagawin ang unang block ng do statements. Kung hindi, gagawin ang pangalawang block ng statement.", "CONTROLS_IF_TOOLTIP_3": "Kung ang unang value ay true, gagawin ang first block ng statement. Kung hindi, kung ang second value ay true, gagawin ang second block ng statement.", "CONTROLS_IF_TOOLTIP_4": "Kung ang first value ay true, gagawin ang first block ng statement. Kung hindi true ang second value, gagawin ang second block ng statement. Kung wala sa mga values ay true, gagawin ang last block ng statements.", @@ -56,7 +56,6 @@ "CONTROLS_IF_ELSEIF_TOOLTIP": "Mag dagdag ng condition sa if block.", "CONTROLS_IF_ELSE_TOOLTIP": "Mag Add ng final, kunin lahat ng condition sa if block.", "LOGIC_COMPARE_TOOLTIP_EQ": "Nag babalik ng true kung ang pinasok ay parehong magkatumbas.", - "LOGIC_COMPARE_TOOLTIP_NEQ": "Return true if both inputs are not equal to each other.", "LOGIC_COMPARE_TOOLTIP_LT": "Nag babalik ng true kung ang unang pinasok ay maliit kaysa sa pangalawang pinasok.", "LOGIC_COMPARE_TOOLTIP_LTE": "Nag babalik ng true kung ang unang pinasok ay maliit sa o katumbas sa pangalawang pinasok.", "LOGIC_COMPARE_TOOLTIP_GT": "Nagbabalik ng true kung ang unang pinasok ay mas malaki kaysa pangalawang pinasok.", diff --git a/msg/json/ur.json b/msg/json/ur.json index cdbe78349..adc8bb485 100644 --- a/msg/json/ur.json +++ b/msg/json/ur.json @@ -6,6 +6,7 @@ "Obaid Raza", "Rizwan", "Sayam Asjad", + "TheAafi", "عثمان خان شاہ", "محمد افضل" ] @@ -16,7 +17,7 @@ "DUPLICATE_BLOCK": "نقل", "ADD_COMMENT": "کمنٹ کریں", "REMOVE_COMMENT": "تبصرہ کو ہٹا دیں", - "DUPLICATE_COMMENT": "نقل تبصرہ", + "DUPLICATE_COMMENT": "دہرا تبصرہ", "EXTERNAL_INPUTS": "خارجی دخل اندازی", "INLINE_INPUTS": "بین السطور داخل کریں", "DELETE_BLOCK": "حذف بلاک", @@ -103,7 +104,7 @@ "LISTS_SET_INDEX_SET": "تعین کریں", "LISTS_SET_INDEX_INSERT": "میں درج کریں", "LISTS_SET_INDEX_INPUT_TO": "بطور", - "LISTS_SET_INDEX_TOOLTIP_SET_FIRST": "فہرست میں پہلا آئٹم کا تعین کریں", + "LISTS_SET_INDEX_TOOLTIP_SET_FIRST": "فہرست میں پہلے آئٹم کا تعین کریں", "PROCEDURES_DEFNORETURN_TITLE": "کو", "PROCEDURES_DEFNORETURN_PROCEDURE": "کچھ کرو", "PROCEDURES_BEFORE_PARAMS": "سمیت:", diff --git a/msg/json/yue.json b/msg/json/yue.json deleted file mode 100644 index cbf201513..000000000 --- a/msg/json/yue.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "@metadata": { - "authors": [ - "Ajeje Brazorf", - "Hello903hello", - "Liuxinyu970226", - "Moon0319" - ] - }, - "TODAY": "今日", - "HELP": "幫手", - "UNDO": "還原", - "REDO": "復原", - "COLOUR_PICKER_HELPURL": "https://zh-yue.wikipedia.org/wiki/色", - "CONTROLS_REPEAT_HELPURL": "https://zh-yue.wikipedia.org/wiki/For_迴圈", - "LISTS_GET_INDEX_RANDOM": "是但", - "DIALOG_OK": "仲可以", - "DIALOG_CANCEL": "取消" -} diff --git a/msg/json/zh-hant.json b/msg/json/zh-hant.json index 6b735e90c..167422e71 100644 --- a/msg/json/zh-hant.json +++ b/msg/json/zh-hant.json @@ -200,7 +200,7 @@ "MATH_ATAN2_TITLE": "X:%1 Y:%2 的 Atan2", "MATH_ATAN2_TOOLTIP": "回傳點(X,Y)從 -180 至 180 度的反正切值。", "TEXT_TEXT_HELPURL": "https://zh.wikipedia.org/wiki/字串", - "TEXT_TEXT_TOOLTIP": "一粒字元、一個字詞或一行字", + "TEXT_TEXT_TOOLTIP": "一個字母、一個字詞或一行字", "TEXT_JOIN_TITLE_CREATEWITH": "字串組合", "TEXT_JOIN_TOOLTIP": "通過連接任意數量的項目來建立一串文字。", "TEXT_CREATE_JOIN_TITLE_JOIN": "加入", @@ -209,7 +209,7 @@ "TEXT_APPEND_TITLE": "至 %1 套用文字 %2", "TEXT_APPEND_TOOLTIP": "添加一些文字到變數「%1」之後。", "TEXT_LENGTH_TITLE": "%1的長度", - "TEXT_LENGTH_TOOLTIP": "返回這串文字的字元數(包含空格)。", + "TEXT_LENGTH_TOOLTIP": "返回這串文字的字母數(包含空格)。", "TEXT_ISEMPTY_TITLE": "%1 為空", "TEXT_ISEMPTY_TOOLTIP": "如果提供的字串為空,則返回 true。", "TEXT_INDEXOF_TOOLTIP": "在字串1中檢索是否有包含字串2,如果有,返回從頭/倒數算起的索引值。如果沒有則返回 %1。", @@ -217,20 +217,20 @@ "TEXT_INDEXOF_OPERATOR_FIRST": "從 最前面 索引字串", "TEXT_INDEXOF_OPERATOR_LAST": "從 最後面 索引字串", "TEXT_CHARAT_TITLE": "在文字 %1 %2", - "TEXT_CHARAT_FROM_START": "取得 字元 #", - "TEXT_CHARAT_FROM_END": "取得倒數第#字元", - "TEXT_CHARAT_FIRST": "擷取首字元", - "TEXT_CHARAT_LAST": "取得 最後一個字元", - "TEXT_CHARAT_RANDOM": "取得 任意字元", - "TEXT_CHARAT_TOOLTIP": "返回位於指定位置的字元。", + "TEXT_CHARAT_FROM_START": "取得第 # 個字母", + "TEXT_CHARAT_FROM_END": "取得倒數第 # 個字母", + "TEXT_CHARAT_FIRST": "取得第一個字母", + "TEXT_CHARAT_LAST": "取得最後一個字母", + "TEXT_CHARAT_RANDOM": "取得隨機字母", + "TEXT_CHARAT_TOOLTIP": "回傳位於指定位置的字母。", "TEXT_GET_SUBSTRING_TOOLTIP": "返回指定的部分文字。", "TEXT_GET_SUBSTRING_INPUT_IN_TEXT": "在字串", - "TEXT_GET_SUBSTRING_START_FROM_START": "取得 字元 #", - "TEXT_GET_SUBSTRING_START_FROM_END": "取得 倒數第 # 個字元", - "TEXT_GET_SUBSTRING_START_FIRST": "取得首字元", - "TEXT_GET_SUBSTRING_END_FROM_START": "到 字元 #", - "TEXT_GET_SUBSTRING_END_FROM_END": "到倒數第#字元", - "TEXT_GET_SUBSTRING_END_LAST": "到尾字元", + "TEXT_GET_SUBSTRING_START_FROM_START": "取得子字串從第 # 個字母", + "TEXT_GET_SUBSTRING_START_FROM_END": "取得子字串從倒數第 # 個字母", + "TEXT_GET_SUBSTRING_START_FIRST": "取得子字串從第一個字母", + "TEXT_GET_SUBSTRING_END_FROM_START": "到第 # 個字母", + "TEXT_GET_SUBSTRING_END_FROM_END": "到倒數第 # 個字母", + "TEXT_GET_SUBSTRING_END_LAST": "到最後一個字母", "TEXT_CHANGECASE_TOOLTIP": "使用不同的大小寫複製這段文字。", "TEXT_CHANGECASE_OPERATOR_UPPERCASE": "轉成英文大寫", "TEXT_CHANGECASE_OPERATOR_LOWERCASE": "轉成英文小寫", diff --git a/package-lock.json b/package-lock.json index 6363468da..c134b5caf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,11 +45,11 @@ "markdown-tables-to-json": "^0.1.7", "mocha": "^10.0.0", "patch-package": "^8.0.0", - "prettier": "3.1.1", + "prettier": "3.2.5", "readline-sync": "^1.4.10", "rimraf": "^5.0.0", "typescript": "^5.0.2", - "webdriverio": "^8.16.7", + "webdriverio": "^8.32.2", "yargs": "^17.2.1" }, "engines": { @@ -1415,14 +1415,14 @@ "dev": true }, "node_modules/@wdio/config": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.29.1.tgz", - "integrity": "sha512-zNUac4lM429HDKAitO+fdlwUH1ACQU8lww+DNVgUyuEb86xgVdTqHeiJr/3kOMJAq9IATeE7mDtYyyn6HPm1JA==", + "version": "8.32.2", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.32.2.tgz", + "integrity": "sha512-ubqe4X+TgcERzXKIpMfisquNxPZNtRU5uPeV7hvas++mD75QyNpmWHCtea2+TjoXKxlZd1MVrtZAwtmqMmyhPw==", "dev": true, "dependencies": { "@wdio/logger": "8.28.0", - "@wdio/types": "8.29.1", - "@wdio/utils": "8.29.1", + "@wdio/types": "8.32.2", + "@wdio/utils": "8.32.2", "decamelize": "^6.0.0", "deepmerge-ts": "^5.0.0", "glob": "^10.2.2", @@ -1499,9 +1499,9 @@ } }, "node_modules/@wdio/protocols": { - "version": "8.24.12", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-8.24.12.tgz", - "integrity": "sha512-QnVj3FkapmVD3h2zoZk+ZQ8gevSj9D9MiIQIy8eOnY4FAneYZ9R9GvoW+mgNcCZO8S8++S/jZHetR8n+8Q808g==", + "version": "8.32.0", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-8.32.0.tgz", + "integrity": "sha512-inLJRrtIGdTz/YPbcsvpSvPlYQFTVtF3OYBwAXhG2FiP1ZwE1CQNLP/xgRGye1ymdGCypGkexRqIx3KBGm801Q==", "dev": true }, "node_modules/@wdio/repl": { @@ -1517,9 +1517,9 @@ } }, "node_modules/@wdio/types": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.29.1.tgz", - "integrity": "sha512-rZYzu+sK8zY1PjCEWxNu4ELJPYKDZRn7HFcYNgR122ylHygfldwkb5TioI6Pn311hQH/S+663KEeoq//Jb0f8A==", + "version": "8.32.2", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.32.2.tgz", + "integrity": "sha512-jq8LcBBQpBP9ZF5kECKEpXv8hN7irCGCjLFAN0Bd5ScRR6qu6MGWvwkDkau2sFPr0b++sKDCEaMzQlwrGFjZXg==", "dev": true, "dependencies": { "@types/node": "^20.1.0" @@ -1529,18 +1529,18 @@ } }, "node_modules/@wdio/utils": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.29.1.tgz", - "integrity": "sha512-Dm91DKL/ZKeZ2QogWT8Twv0p+slEgKyB/5x9/kcCG0Q2nNa+tZedTjOhryzrsPiWc+jTSBmjGE4katRXpJRFJg==", + "version": "8.32.2", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.32.2.tgz", + "integrity": "sha512-PJcP4d1Fr8Zp+YIfGN93G0fjDj/6J0I6Gf6p0IpJk8qKQpdFDm4gB+lc202iv2YkyC+oT6b4Ik2W9LzvpSKNoQ==", "dev": true, "dependencies": { "@puppeteer/browsers": "^1.6.0", "@wdio/logger": "8.28.0", - "@wdio/types": "8.29.1", + "@wdio/types": "8.32.2", "decamelize": "^6.0.0", "deepmerge-ts": "^5.1.0", "edgedriver": "^5.3.5", - "geckodriver": "^4.2.0", + "geckodriver": "^4.3.1", "get-port": "^7.0.0", "import-meta-resolve": "^4.0.0", "locate-app": "^2.1.0", @@ -1585,6 +1585,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@wdio/utils/node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@wdio/utils/node_modules/https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/@wdio/utils/node_modules/lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", @@ -3565,9 +3591,9 @@ } }, "node_modules/devtools-protocol": { - "version": "0.0.1249869", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1249869.tgz", - "integrity": "sha512-Ctp4hInA0BEavlUoRy9mhGq0i+JSo/AwVyX2EFgZmV1kYB+Zq+EMBAn52QWu6FbRr10hRb6pBl420upbp4++vg==", + "version": "0.0.1261483", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1261483.tgz", + "integrity": "sha512-7vJvejpzA5DTfZVkr7a8sGpEAzEiAqcgmRTB0LSUrWeOicwL09lMQTzxHtFNVhJ1OOJkgYdH6Txvy9E5j3VOUQ==", "dev": true }, "node_modules/dir-glob": { @@ -3713,13 +3739,13 @@ } }, "node_modules/edgedriver": { - "version": "5.3.9", - "resolved": "https://registry.npmjs.org/edgedriver/-/edgedriver-5.3.9.tgz", - "integrity": "sha512-G0wNgFMFRDnFfKaXG2R6HiyVHqhKwdQ3EgoxW3wPlns2wKqem7F+HgkWBcevN7Vz0nN4AXtskID7/6jsYDXcKw==", + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/edgedriver/-/edgedriver-5.3.10.tgz", + "integrity": "sha512-RFSHYMNtcF1PjaGZCA2rdQQ8hSTLPZgcYgeY1V6dC+tR4NhZXwFAku+8hCbRYh7ZlwKKrTbVu9FwknjFddIuuw==", "dev": true, "hasInstallScript": true, "dependencies": { - "@wdio/logger": "^8.16.17", + "@wdio/logger": "^8.28.0", "decamelize": "^6.0.0", "edge-paths": "^3.0.5", "node-fetch": "^3.3.2", @@ -4960,13 +4986,13 @@ "dev": true }, "node_modules/geckodriver": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-4.3.1.tgz", - "integrity": "sha512-ol7JLsj55o5k+z7YzeSy2mdJROXMAxIa+uzr3A1yEMr5HISqQOTslE3ZeARcxR4jpAY3fxmHM+sq32qbe/eXfA==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-4.3.2.tgz", + "integrity": "sha512-TNOoy+ULXJWI5XOq7CXD3PAD9TJa4NjMe7nKUXjlIsf+vezuaRsFgPwcgYdEem1K7106wabYsqr7Kqn51g0sJg==", "dev": true, "hasInstallScript": true, "dependencies": { - "@wdio/logger": "^8.24.12", + "@wdio/logger": "^8.28.0", "decamelize": "^6.0.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", @@ -5003,6 +5029,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/geckodriver/node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/geckodriver/node_modules/https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/geckodriver/node_modules/isexe": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", @@ -7451,12 +7503,12 @@ } }, "node_modules/locate-app": { - "version": "2.2.14", - "resolved": "https://registry.npmjs.org/locate-app/-/locate-app-2.2.14.tgz", - "integrity": "sha512-fqGE0IHZ3v+9kCjYvhwrP52aTGP1itOfp4TZZuv4dNl2gKN/pHCIlMhDSqPDb3qJ5Rti39y5T+/XrfCsiDRjKw==", + "version": "2.2.19", + "resolved": "https://registry.npmjs.org/locate-app/-/locate-app-2.2.19.tgz", + "integrity": "sha512-mjhvrYRHnLAVwreShl8NTwq9EUyfRoCqB0UsOlMKXo2KBmtb4dhlHbZH4mcfDsoNoLkHZ1Rq4TsWP/59Ix62Ww==", "dev": true, "dependencies": { - "n12": "1.8.17", + "n12": "1.8.22", "type-fest": "2.13.0", "userhome": "1.0.0" } @@ -8153,9 +8205,9 @@ } }, "node_modules/n12": { - "version": "1.8.17", - "resolved": "https://registry.npmjs.org/n12/-/n12-1.8.17.tgz", - "integrity": "sha512-/NdfkU7nyqq70E4RvDa3OrR/wkZrYjDGXjn4JgIZnn+ULcyW1f6BLjNqyFC+ND9FqzyWjcQhvagFJmVJ8k8Lew==", + "version": "1.8.22", + "resolved": "https://registry.npmjs.org/n12/-/n12-1.8.22.tgz", + "integrity": "sha512-nzPCOuLOIoUuninAMRXfrbkB7O9XkWS7iv7fzDW1pRUaQhMpatj8iX55evwcNRWnm0UF29uuoHpwubYbsV7OGw==", "dev": true }, "node_modules/nanoid": { @@ -9106,9 +9158,9 @@ } }, "node_modules/prettier": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz", - "integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -11033,9 +11085,9 @@ "dev": true }, "node_modules/undici": { - "version": "5.26.3", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.26.3.tgz", - "integrity": "sha512-H7n2zmKEWgOllKkIUkLvFmsJQj062lSm3uA4EYApG8gLuiOM0/go9bIoC3HVaSnfg4xunowDE2i9p8drkXuvDw==", + "version": "5.28.3", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.3.tgz", + "integrity": "sha512-3ItfzbrhDlINjaP0duwnNsKpDQk3acHI3gVJ1z4fmwMK31k5G9OVIAMLSIaP6w4FaGkaAkN6zaQO9LUvZ1t7VA==", "dev": true, "dependencies": { "@fastify/busboy": "^2.0.0" @@ -11441,27 +11493,27 @@ } }, "node_modules/web-streams-polyfill": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.2.tgz", - "integrity": "sha512-3pRGuxRF5gpuZc0W+EpwQRmCD7gRqcDOMt688KmdlDAgAyaB1XlN0zq2njfDNm44XVdIouE7pZ6GzbdyH47uIQ==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", "dev": true, "engines": { "node": ">= 8" } }, "node_modules/webdriver": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.29.1.tgz", - "integrity": "sha512-D3gkbDUxFKBJhNHRfMriWclooLbNavVQC1MRvmENAgPNKaHnFn+M+WtP9K2sEr0XczLGNlbOzT7CKR9K5UXKXA==", + "version": "8.32.2", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.32.2.tgz", + "integrity": "sha512-uyCT2QzCqoz+EsMLTApG5/+RvHJR9MVbdEnjMoxpJDt+IeZCG2Vy/Gq9oNgNQfpxrvZme/EY+PtBsltZi7BAyg==", "dev": true, "dependencies": { "@types/node": "^20.1.0", "@types/ws": "^8.5.3", - "@wdio/config": "8.29.1", + "@wdio/config": "8.32.2", "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.24.12", - "@wdio/types": "8.29.1", - "@wdio/utils": "8.29.1", + "@wdio/protocols": "8.32.0", + "@wdio/types": "8.32.2", + "@wdio/utils": "8.32.2", "deepmerge-ts": "^5.1.0", "got": "^12.6.1", "ky": "^0.33.0", @@ -11472,23 +11524,23 @@ } }, "node_modules/webdriverio": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.29.1.tgz", - "integrity": "sha512-NZK95ivXCqdPraB3FHMw6ByxnCvtgFXkjzG2l3Oq5z0IuJS2aMow3AKFIyiuG6is/deGCe+Tb8eFTCqak7UV+w==", + "version": "8.32.2", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.32.2.tgz", + "integrity": "sha512-Z0Wc/dHFfWGWJZpaQ8u910/LG0E9EIVTO7J5yjqWx2XtXz2LzQMxYwNRnvNLhY/1tI4y/cZxI6kFMWr8wD2TtA==", "dev": true, "dependencies": { "@types/node": "^20.1.0", - "@wdio/config": "8.29.1", + "@wdio/config": "8.32.2", "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.24.12", + "@wdio/protocols": "8.32.0", "@wdio/repl": "8.24.12", - "@wdio/types": "8.29.1", - "@wdio/utils": "8.29.1", + "@wdio/types": "8.32.2", + "@wdio/utils": "8.32.2", "archiver": "^6.0.0", "aria-query": "^5.0.0", "css-shorthand-properties": "^1.1.1", "css-value": "^0.0.1", - "devtools-protocol": "^0.0.1249869", + "devtools-protocol": "^0.0.1261483", "grapheme-splitter": "^1.0.2", "import-meta-resolve": "^4.0.0", "is-plain-obj": "^4.1.0", @@ -11500,7 +11552,7 @@ "resq": "^1.9.1", "rgb2hex": "0.2.5", "serialize-error": "^11.0.1", - "webdriver": "8.29.1" + "webdriver": "8.32.2" }, "engines": { "node": "^16.13 || >=18" diff --git a/package.json b/package.json index 36921f2fc..18eab0860 100644 --- a/package.json +++ b/package.json @@ -97,11 +97,11 @@ "markdown-tables-to-json": "^0.1.7", "mocha": "^10.0.0", "patch-package": "^8.0.0", - "prettier": "3.1.1", + "prettier": "3.2.5", "readline-sync": "^1.4.10", "rimraf": "^5.0.0", "typescript": "^5.0.2", - "webdriverio": "^8.16.7", + "webdriverio": "^8.32.2", "yargs": "^17.2.1" }, "dependencies": { diff --git a/scripts/gulpfiles/docs_tasks.js b/scripts/gulpfiles/docs_tasks.js index 8885af24a..8820a586f 100644 --- a/scripts/gulpfiles/docs_tasks.js +++ b/scripts/gulpfiles/docs_tasks.js @@ -95,7 +95,11 @@ const createToc = function(done) { const files = fs.readdirSync(DOCS_DIR); const map = buildAlternatePathsMap(files); const referencePath = '/blockly/reference/js'; - fs.writeSync(toc, 'toc:\n'); + + const tocHeader = `toc: +- title: Overview + path: /blockly/reference/js/blockly.md\n`; + fs.writeSync(toc, tocHeader); // Generate a section of TOC for each section/heading in the overview file. const sections = fileContent.split('##'); diff --git a/tests/scripts/check_metadata.sh b/tests/scripts/check_metadata.sh index 1accb7148..f8d7b7457 100755 --- a/tests/scripts/check_metadata.sh +++ b/tests/scripts/check_metadata.sh @@ -37,7 +37,8 @@ readonly RELEASE_DIR='dist' # Q2 2023 9.3.3 887618 # Q3 2023 10.1.3 898859 # Q4 2023 10.2.2 903535 -readonly BLOCKLY_SIZE_EXPECTED=903535 +# Q1 2024 10.3.1 914366 +readonly BLOCKLY_SIZE_EXPECTED=914366 # Size of blocks_compressed.js # Q2 2019 2.20190722.0 75618 @@ -60,6 +61,7 @@ readonly BLOCKLY_SIZE_EXPECTED=903535 # Q2 2023 9.3.3 91848 # Q3 2023 10.1.3 90150 # Q4 2023 10.2.2 90269 +# Q1 2024 10.3.1 90269 readonly BLOCKS_SIZE_EXPECTED=90269 # Size of blockly_compressed.js.gz @@ -84,7 +86,8 @@ readonly BLOCKS_SIZE_EXPECTED=90269 # Q2 2023 9.3.3 175206 # Q3 2023 10.1.3 180553 # Q4 2023 10.2.2 181474 -readonly BLOCKLY_GZ_SIZE_EXPECTED=181474 +# Q1 2024 10.3.1 184237 +readonly BLOCKLY_GZ_SIZE_EXPECTED=184237 # Size of blocks_compressed.js.gz # Q2 2019 2.20190722.0 14552 @@ -107,7 +110,8 @@ readonly BLOCKLY_GZ_SIZE_EXPECTED=181474 # Q2 2023 9.3.3 16736 # Q3 2023 10.1.3 16508 # Q4 2023 10.2.2 16442 -readonly BLOCKS_GZ_SIZE_EXPECTED=16442 +# Q1 2024 10.3.1 16533 +readonly BLOCKS_GZ_SIZE_EXPECTED=16533 # ANSI colors readonly BOLD_GREEN='\033[1;32m' From fc4228ce0374ab2c818df05625edb71749db804b Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Mon, 11 Mar 2024 19:12:35 +0000 Subject: [PATCH 18/92] feat: add comment view (for workspace comments, and block comments for partners) (#7914) * feat: add basic comment view * feat: add icons to comment * chore: add text area to comment view * feat: add getting size * feat: add collapsing comment view * feat: add setting editability * feat: add location and text hooks. * feat: add changing the size * feat: resizing * feat: add collapsing * feat: add disposing * feat: add cursors * feat: add moving to the front * chore: split construction into subprocedures * chore: split resizing into subprocedures * feat: handle RTL * chore: add doc comments throughout file * chore: reduce css specificity where possible * chore: format * feat: add remove change listener methods * chore: add tests for listeners * feat: add disposing accessors * chore: add coordinate system notes * chore: add issues to TODOs where possible * chore: remove suite.only --- core/blockly.ts | 2 + core/comments.ts | 7 + core/comments/comment_view.ts | 741 ++++++++++++++++++++++ core/inject.ts | 5 +- media/arrow-dropdown.svg | 1 + media/resize-handle.svg | 3 + tests/mocha/index.html | 2 + tests/mocha/old_workspace_comment_test.js | 267 ++++++++ tests/mocha/workspace_comment_test.js | 400 +++++------- 9 files changed, 1193 insertions(+), 235 deletions(-) create mode 100644 core/comments.ts create mode 100644 core/comments/comment_view.ts create mode 100644 media/arrow-dropdown.svg create mode 100644 media/resize-handle.svg create mode 100644 tests/mocha/old_workspace_comment_test.js diff --git a/core/blockly.ts b/core/blockly.ts index 2a5dba653..e68199854 100644 --- a/core/blockly.ts +++ b/core/blockly.ts @@ -36,6 +36,7 @@ import {ConnectionType} from './connection_type.js'; import * as ContextMenu from './contextmenu.js'; import * as ContextMenuItems from './contextmenu_items.js'; import {ContextMenuRegistry} from './contextmenu_registry.js'; +import * as comments from './comments.js'; import * as Css from './css.js'; import {DeleteArea} from './delete_area.js'; import * as dialog from './dialog.js'; @@ -480,6 +481,7 @@ export {ConnectionType}; export {ConnectionChecker}; export {ConnectionDB}; export {ContextMenuRegistry}; +export {comments}; export {Cursor}; export {DeleteArea}; export {DragTarget}; diff --git a/core/comments.ts b/core/comments.ts new file mode 100644 index 000000000..d7a9aed3a --- /dev/null +++ b/core/comments.ts @@ -0,0 +1,7 @@ +/** + * @license + * Copyright 2024 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +export {CommentView} from './comments/comment_view.js'; diff --git a/core/comments/comment_view.ts b/core/comments/comment_view.ts new file mode 100644 index 000000000..21d2ebcfd --- /dev/null +++ b/core/comments/comment_view.ts @@ -0,0 +1,741 @@ +/** + * @license + * Copyright 2024 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {IRenderedElement} from '../interfaces/i_rendered_element.js'; +import {WorkspaceSvg} from '../workspace_svg.js'; +import * as dom from '../utils/dom.js'; +import {Svg} from '../utils/svg.js'; +import * as layers from '../layers.js'; +import * as css from '../css.js'; +import {Coordinate, Size, browserEvents} from '../utils.js'; +import * as touch from '../touch.js'; + +const MIN_SIZE = new Size(100, 60); +export class CommentView implements IRenderedElement { + /** The root group element of the comment view. */ + private svgRoot: SVGGElement; + + /** The rect background for the top bar. */ + private topBar: SVGRectElement; + + /** The delete icon that goes in the top bar. */ + private deleteIcon: SVGImageElement; + + /** The foldout icon that goes in the top bar. */ + private foldoutIcon: SVGImageElement; + + /** The text element that goes in the top bar. */ + private textPreview: SVGTextElement; + + /** The actual text node in the text preview. */ + private textPreviewNode: Text; + + /** The resize handle element. */ + private resizeHandle: SVGImageElement; + + /** The foreignObject containing the HTML text area. */ + private foreignObject: SVGForeignObjectElement; + + /** The text area where the user can type. */ + private textArea: HTMLTextAreaElement; + + /** The current size of the comment in workspace units. */ + private size: Size = new Size(120, 100); + + /** Whether the comment is collapsed or not. */ + private collapsed: boolean = false; + + /** Whether the comment is editable or not. */ + private editable: boolean = true; + + /** The current location of the comment in workspace coordinates. */ + private location: Coordinate = new Coordinate(0, 0); + + /** The current text of the comment. Updates on text area change. */ + private text: string = ''; + + /** Listeners for changes to text. */ + private textChangeListeners: Array< + (oldText: string, newText: string) => void + > = []; + + /** Listeners for changes to size. */ + private sizeChangeListeners: Array<(oldSize: Size, newSize: Size) => void> = + []; + + /** Listeners for disposal. */ + private disposeListeners: Array<() => void> = []; + + /** Listeners for collapsing. */ + private collapseChangeListeners: Array<(newCollapse: boolean) => void> = []; + + /** + * Event data for the pointer up event on the resize handle. Used to + * unregister the listener. + */ + private resizePointerUpListener: browserEvents.Data | null = null; + + /** + * Event data for the pointer move event on the resize handle. Used to + * unregister the listener. + */ + private resizePointerMoveListener: browserEvents.Data | null = null; + + /** Whether this comment view is currently being disposed or not. */ + private disposing = false; + + /** Whether this comment view has been disposed or not. */ + private disposed = false; + + constructor(private readonly workspace: WorkspaceSvg) { + this.svgRoot = dom.createSvgElement(Svg.G, { + 'class': 'blocklyComment blocklyEditable', + }); + + ({ + topBar: this.topBar, + deleteIcon: this.deleteIcon, + foldoutIcon: this.foldoutIcon, + textPreview: this.textPreview, + textPreviewNode: this.textPreviewNode, + } = this.createTopBar(this.svgRoot, workspace)); + + ({foreignObject: this.foreignObject, textArea: this.textArea} = + this.createTextArea(this.svgRoot)); + + this.resizeHandle = this.createResizeHandle(this.svgRoot, workspace); + + // TODO: Remove this comment before merging. + // I think we want comments to exist on the same layer as blocks. + workspace.getLayerManager()?.append(this, layers.BLOCK); + + // Set size to the default size. + this.setSize(this.size); + } + + /** + * Creates the top bar and the elements visually within it. + * Registers event listeners. + */ + private createTopBar( + svgRoot: SVGGElement, + workspace: WorkspaceSvg, + ): { + topBar: SVGRectElement; + deleteIcon: SVGImageElement; + foldoutIcon: SVGImageElement; + textPreview: SVGTextElement; + textPreviewNode: Text; + } { + const topBar = dom.createSvgElement( + Svg.RECT, + { + 'class': 'blocklyCommentTopbar', + 'x': 0, + 'y': 0, + }, + svgRoot, + ); + // TODO: Before merging, does this mean to override an individual image, + // folks need to replace the whole media folder? + const deleteIcon = dom.createSvgElement( + Svg.IMAGE, + { + 'class': 'blocklyDeleteIcon', + 'href': `${workspace.options.pathToMedia}delete-icon.svg`, + }, + svgRoot, + ); + const foldoutIcon = dom.createSvgElement( + Svg.IMAGE, + { + 'class': 'blocklyFoldoutIcon', + 'href': `${workspace.options.pathToMedia}arrow-dropdown.svg`, + }, + svgRoot, + ); + const textPreview = dom.createSvgElement( + Svg.TEXT, + { + 'class': 'blocklyCommentPreview blocklyCommentText blocklyText', + }, + svgRoot, + ); + const textPreviewNode = document.createTextNode(''); + textPreview.appendChild(textPreviewNode); + + // TODO(toychest): Triggering this on pointerdown means that we can't start + // drags on the foldout icon. We need to open up the gesture system + // to fix this. + browserEvents.conditionalBind( + foldoutIcon, + 'pointerdown', + this, + this.onFoldoutDown, + ); + browserEvents.conditionalBind( + deleteIcon, + 'pointerdown', + this, + this.onDeleteDown, + ); + + return {topBar, deleteIcon, foldoutIcon, textPreview, textPreviewNode}; + } + + /** + * Creates the text area where users can type. Registers event listeners. + */ + private createTextArea(svgRoot: SVGGElement): { + foreignObject: SVGForeignObjectElement; + textArea: HTMLTextAreaElement; + } { + const foreignObject = dom.createSvgElement( + Svg.FOREIGNOBJECT, + { + 'class': 'blocklyCommentForeignObject', + }, + svgRoot, + ); + const body = document.createElementNS(dom.HTML_NS, 'body'); + body.setAttribute('xmlns', dom.HTML_NS); + body.className = 'blocklyMinimalBody'; + const textArea = document.createElementNS( + dom.HTML_NS, + 'textarea', + ) as HTMLTextAreaElement; + dom.addClass(textArea, 'blocklyCommentText'); + dom.addClass(textArea, 'blocklyTextarea'); + dom.addClass(textArea, 'blocklyText'); + body.appendChild(textArea); + foreignObject.appendChild(body); + + browserEvents.conditionalBind(textArea, 'change', this, this.onTextChange); + + return {foreignObject, textArea}; + } + + /** Creates the DOM elements for the comment resize handle. */ + private createResizeHandle( + svgRoot: SVGGElement, + workspace: WorkspaceSvg, + ): SVGImageElement { + const resizeHandle = dom.createSvgElement( + Svg.IMAGE, + { + 'class': 'blocklyResizeHandle', + 'href': `${workspace.options.pathToMedia}resize-handle.svg`, + }, + svgRoot, + ); + + browserEvents.conditionalBind( + resizeHandle, + 'pointerdown', + this, + this.onResizePointerDown, + ); + + return resizeHandle; + } + + /** Returns the root SVG group element of the comment view. */ + getSvgRoot(): SVGGElement { + return this.svgRoot; + } + + /** Returns the current size of the comment in workspace units. */ + getSize(): Size { + return this.size; + } + + /** + * Sets the size of the comment in workspace units, and updates the view + * elements to reflect the new size. + */ + setSize(size: Size) { + size = new Size( + Math.max(size.width, MIN_SIZE.width), + Math.max(size.height, MIN_SIZE.height), + ); + + const oldSize = this.size; + this.size = size; + const topBarSize = this.topBar.getBBox(); + const deleteSize = this.deleteIcon.getBBox(); + const foldoutSize = this.foldoutIcon.getBBox(); + const textPreviewSize = this.textPreview.getBBox(); + const resizeSize = this.resizeHandle.getBBox(); + + this.svgRoot.setAttribute('height', `${size.height}`); + this.svgRoot.setAttribute('width', `${size.width}`); + + this.topBar.setAttribute('width', `${size.width}`); + + this.updateTextAreaSize(size, topBarSize); + this.updateDeleteIconPosition(size, topBarSize, deleteSize); + this.updateFoldoutIconPosition(topBarSize, foldoutSize); + this.updateTextPreviewSize( + size, + topBarSize, + textPreviewSize, + deleteSize, + resizeSize, + ); + + this.resizeHandle.setAttribute('x', `${size.width - resizeSize.width}`); + this.resizeHandle.setAttribute('y', `${size.height - resizeSize.height}`); + + this.onSizeChange(oldSize, this.size); + } + + /** Updates the size of the text area elements to reflect the new size. */ + private updateTextAreaSize(size: Size, topBarSize: Size) { + this.foreignObject.setAttribute( + 'height', + `${size.height - topBarSize.height}`, + ); + this.foreignObject.setAttribute('width', `${size.width}`); + this.foreignObject.setAttribute('y', `${topBarSize.height}`); + if (this.workspace.RTL) { + this.foreignObject.setAttribute('x', `${-size.width}`); + } + } + + /** + * Updates the position of the delete icon elements to reflect the new size. + */ + private updateDeleteIconPosition( + size: Size, + topBarSize: Size, + deleteSize: Size, + ) { + const deleteMargin = (topBarSize.height - deleteSize.height) / 2; + this.deleteIcon.setAttribute('y', `${deleteMargin}`); + this.deleteIcon.setAttribute( + 'x', + `${size.width - deleteSize.width - deleteMargin}`, + ); + } + + /** + * Updates the position of the foldout icon elements to reflect the new size. + */ + private updateFoldoutIconPosition(topBarSize: Size, foldoutSize: Size) { + const foldoutMargin = (topBarSize.height - foldoutSize.height) / 2; + this.foldoutIcon.setAttribute('y', `${foldoutMargin}`); + this.foldoutIcon.setAttribute('x', `${foldoutMargin}`); + } + + /** + * Updates the size and position of the text preview elements to reflect the new size. + */ + private updateTextPreviewSize( + size: Size, + topBarSize: Size, + textPreviewSize: Size, + deleteSize: Size, + foldoutSize: Size, + ) { + const textPreviewMargin = (topBarSize.height - textPreviewSize.height) / 2; + const deleteMargin = (topBarSize.height - deleteSize.height) / 2; + const foldoutMargin = (topBarSize.height - foldoutSize.height) / 2; + + const textPreviewWidth = + size.width - + foldoutSize.width - + foldoutMargin * 2 - + deleteSize.width - + deleteMargin * 2; + this.textPreview.setAttribute( + 'x', + `${ + foldoutSize.width + foldoutMargin * 2 * (this.workspace.RTL ? -1 : 1) + }`, + ); + this.textPreview.setAttribute( + 'y', + `${textPreviewMargin + textPreviewSize.height / 2}`, + ); + this.textPreview.setAttribute('width', `${textPreviewWidth}`); + } + + /** + * Triggers listeners when the size of the comment changes, either + * progrmatically or manually by the user. + */ + private onSizeChange(oldSize: Size, newSize: Size) { + // Loop through listeners backwards in case they remove themselves. + for (let i = this.sizeChangeListeners.length - 1; i >= 0; i--) { + this.sizeChangeListeners[i](oldSize, newSize); + } + } + + /** + * Registers a callback that listens for size changes. + * + * @param listener Receives callbacks when the size of the comment changes. + * The new and old size are in workspace units. + */ + addSizeChangeListener(listener: (oldSize: Size, newSize: Size) => void) { + this.sizeChangeListeners.push(listener); + } + + /** Removes the given listener from the list of size change listeners. */ + removeSizeChangeListener(listener: () => void) { + this.sizeChangeListeners.splice( + this.sizeChangeListeners.indexOf(listener), + 1, + ); + } + + /** + * Handles starting an interaction with the resize handle to resize the + * comment. + */ + private onResizePointerDown(e: PointerEvent) { + this.bringToFront(); + if (browserEvents.isRightButton(e)) { + e.stopPropagation(); + return; + } + + // TODO(#7926): Move this into a utils file. + this.workspace.startDrag( + e, + new Coordinate( + this.workspace.RTL ? -this.getSize().width : this.getSize().width, + this.getSize().height, + ), + ); + + this.resizePointerUpListener = browserEvents.conditionalBind( + document, + 'pointerup', + this, + this.onResizePointerUp, + ); + this.resizePointerMoveListener = browserEvents.conditionalBind( + document, + 'pointermove', + this, + this.onResizePointerMove, + ); + + this.workspace.hideChaff(); + + e.stopPropagation(); + } + + /** Ends an interaction with the resize handle. */ + private onResizePointerUp(_e: PointerEvent) { + touch.clearTouchIdentifier(); + if (this.resizePointerUpListener) { + browserEvents.unbind(this.resizePointerUpListener); + this.resizePointerUpListener = null; + } + if (this.resizePointerMoveListener) { + browserEvents.unbind(this.resizePointerMoveListener); + this.resizePointerMoveListener = null; + } + } + + /** Resizes the comment in response to a drag on the resize handle. */ + private onResizePointerMove(e: PointerEvent) { + // TODO(#7926): Move this into a utils file. + const delta = this.workspace.moveDrag(e); + this.setSize(new Size(this.workspace.RTL ? -delta.x : delta.x, delta.y)); + } + + /** Returns true if the comment is currently collapsed. */ + isCollapsed(): boolean { + return this.collapsed; + } + + /** Sets whether the comment is currently collapsed or not. */ + setCollapsed(collapsed: boolean) { + this.collapsed = collapsed; + if (collapsed) { + dom.addClass(this.svgRoot, 'blocklyCollapsed'); + } else { + dom.removeClass(this.svgRoot, 'blocklyCollapsed'); + } + // Repositions resize handle and such. + this.setSize(this.size); + this.onCollapse(); + } + + /** + * Triggers listeners when the collapsed-ness of the comment changes, either + * progrmatically or manually by the user. + */ + private onCollapse() { + // Loop through listeners backwards in case they remove themselves. + for (let i = this.collapseChangeListeners.length - 1; i >= 0; i--) { + this.collapseChangeListeners[i](this.collapsed); + } + } + + /** Registers a callback that listens for collapsed-ness changes. */ + addOnCollapseListener(listener: (newCollapse: boolean) => void) { + this.collapseChangeListeners.push(listener); + } + + /** Removes the given listener from the list of on collapse listeners. */ + removeOnCollapseListener(listener: () => void) { + this.collapseChangeListeners.splice( + this.collapseChangeListeners.indexOf(listener), + 1, + ); + } + + /** + * Toggles the collapsedness of the block when we receive a pointer down + * event on the foldout icon. + */ + private onFoldoutDown(e: PointerEvent) { + this.bringToFront(); + if (browserEvents.isRightButton(e)) { + e.stopPropagation(); + return; + } + + this.setCollapsed(!this.collapsed); + + this.workspace.hideChaff(); + + e.stopPropagation(); + } + + /** Returns true if the comment is currently editable. */ + isEditable(): boolean { + return this.editable; + } + + /** Sets the editability of the comment. */ + setEditable(editable: boolean) { + this.editable = editable; + if (this.editable) { + dom.addClass(this.svgRoot, 'blocklyEditable'); + dom.removeClass(this.svgRoot, 'blocklyReadonly'); + this.textArea.removeAttribute('readonly'); + } else { + dom.removeClass(this.svgRoot, 'blocklyEditable'); + dom.addClass(this.svgRoot, 'blocklyReadonly'); + this.textArea.setAttribute('readonly', 'true'); + } + } + + /** Returns the current location of the comment in workspace coordinates. */ + getRelativeToSurfaceXY(): Coordinate { + return this.location; + } + + /** + * Moves the comment view to the given location. + * + * @param location The location to move to in workspace coordinates. + */ + moveTo(location: Coordinate) { + this.location = location; + this.svgRoot.setAttribute( + 'transform', + `translate(${location.x}, ${location.y})`, + ); + } + + /** Retursn the current text of the comment. */ + getText() { + return this.text; + } + + /** Sets the current text of the comment. */ + setText(text: string) { + this.textArea.value = text; + this.onTextChange(); + } + + /** Registers a callback that listens for text changes. */ + addTextChangeListener(listener: (oldText: string, newText: string) => void) { + this.textChangeListeners.push(listener); + } + + /** Removes the given listener from the list of text change listeners. */ + removeTextChangeListener(listener: () => void) { + this.textChangeListeners.splice( + this.textChangeListeners.indexOf(listener), + 1, + ); + } + + /** + * Triggers listeners when the text of the comment changes, either + * progrmatically or manually by the user. + */ + private onTextChange() { + const oldText = this.text; + this.text = this.textArea.value; + this.textPreviewNode.textContent = this.truncateText(this.text); + // Loop through listeners backwards in case they remove themselves. + for (let i = this.textChangeListeners.length - 1; i >= 0; i--) { + this.textChangeListeners[i](oldText, this.text); + } + } + + /** Truncates the text to fit within the top view. */ + private truncateText(text: string): string { + return text.length >= 12 ? `${text.substring(0, 9)}...` : text; + } + + /** Brings the workspace comment to the front of its layer. */ + private bringToFront() { + const parent = this.svgRoot.parentNode; + const childNodes = parent!.childNodes; + // Avoid moving the comment if it's already at the bottom. + if (childNodes[childNodes.length - 1] !== this.svgRoot) { + parent!.appendChild(this.svgRoot); + } + } + + /** + * Handles disposing of the comment when we get a pointer down event on the + * delete icon. + */ + private onDeleteDown(e: PointerEvent) { + if (browserEvents.isRightButton(e)) { + e.stopPropagation(); + return; + } + + this.dispose(); + e.stopPropagation(); + } + + /** Disposes of this comment view. */ + dispose() { + this.disposing = true; + dom.removeNode(this.svgRoot); + // Loop through listeners backwards in case they remove themselves. + for (let i = this.disposeListeners.length - 1; i >= 0; i--) { + this.disposeListeners[i](); + } + this.disposed = true; + } + + /** Returns whether this comment view has been disposed or not. */ + isDisposed(): boolean { + return this.disposed; + } + + /** + * Returns true if this comment view is currently being disposed or has + * already been disposed. + */ + isDeadOrDying(): boolean { + return this.disposing || this.disposed; + } + + /** Registers a callback that listens for disposal of this view. */ + addDisposeListener(listener: () => void) { + this.disposeListeners.push(listener); + } + + /** Removes the given listener from the list of disposal listeners. */ + removeDisposeListener(listener: () => void) { + this.disposeListeners.splice(this.disposeListeners.indexOf(listener), 1); + } +} + +css.register(` +.blocklyWorkspace { + --commentFillColour: #FFFCC7; + --commentBorderColour: #F2E49B; + --commentIconColour: #1A1A1A +} + +.blocklyComment .blocklyTextarea { + background-color: var(--commentFillColour); + border: 1px solid var(--commentBorderColour); + outline: 0; + resize: none; + overflow: hidden; + box-sizing: border-box; + padding: 8px; + width: 100%; + height: 100%; + display: block; +} + +.blocklyReadonly.blocklyComment .blocklyTextarea { + cursor: inherit; +} + +.blocklyDeleteIcon { + width: 20px; + height: 20px; + display: none; + fill: var(--commentIconColour); + cursor: pointer; +} + +.blocklyFoldoutIcon { + width: 20px; + height: 20px; + fill: var(--commentIconColour); + transform-origin: 12px 12px; + cursor: pointer; +} +.blocklyResizeHandle { + width: 12px; + height: 12px; + stroke: var(--commentIconColour); + cursor: se-resize; +} + +.blocklyCommentTopbar { + fill: var(--commentBorderColour); + height: 24px; +} + +.blocklyComment .blocklyCommentPreview.blocklyText { + fill: var(--commentIconColour); + dominant-baseline: middle; + display: none; +} + +.blocklyCollapsed.blocklyComment .blocklyCommentPreview { + display: block; +} + +.blocklyCollapsed.blocklyComment .blocklyCommentForeignObject, +.blocklyCollapsed.blocklyComment .blocklyResizeHandle { + display: none; +} + +.blocklyCollapsed.blocklyComment .blocklyFoldoutIcon { + transform: rotate(-90deg); +} + +.blocklyRTL .blocklyComment { + transform: scale(-1, 1); +} + +.blocklyRTL .blocklyCommentForeignObject { + /* Revert the scale and control RTL using direction instead. */ + transform: scale(-1, 1); + direction: rtl; +} + +.blocklyRTL .blocklyCommentPreview { + /* Revert the scale and control RTL using direction instead. */ + transform: scale(-1, 1); + direction: rtl; +} + +.blocklyRTL .blocklyResizeHandle { + cursor: sw-resize; +} +`); diff --git a/core/inject.ts b/core/inject.ts index b938abaa4..323191817 100644 --- a/core/inject.ts +++ b/core/inject.ts @@ -53,7 +53,10 @@ export function inject( } const options = new Options(opt_options || ({} as BlocklyOptions)); const subContainer = document.createElement('div'); - subContainer.className = 'injectionDiv'; + dom.addClass(subContainer, 'injectionDiv'); + if (opt_options?.rtl) { + dom.addClass(subContainer, 'blocklyRTL'); + } subContainer.tabIndex = 0; aria.setState(subContainer, aria.State.LABEL, Msg['WORKSPACE_ARIA_LABEL']); diff --git a/media/arrow-dropdown.svg b/media/arrow-dropdown.svg new file mode 100644 index 000000000..7aeb5b174 --- /dev/null +++ b/media/arrow-dropdown.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/media/resize-handle.svg b/media/resize-handle.svg new file mode 100644 index 000000000..4002304e2 --- /dev/null +++ b/media/resize-handle.svg @@ -0,0 +1,3 @@ + + + diff --git a/tests/mocha/index.html b/tests/mocha/index.html index 6c4e5ad0c..7cebc68ed 100644 --- a/tests/mocha/index.html +++ b/tests/mocha/index.html @@ -106,6 +106,8 @@ import './metrics_test.js'; import './mutator_test.js'; import './names_test.js'; + // TODO: Remove these tests. + import './old_workspace_comment_test.js'; import './procedure_map_test.js'; import './blocks/procedures_test.js'; import './registry_test.js'; diff --git a/tests/mocha/old_workspace_comment_test.js b/tests/mocha/old_workspace_comment_test.js new file mode 100644 index 000000000..f2126dea2 --- /dev/null +++ b/tests/mocha/old_workspace_comment_test.js @@ -0,0 +1,267 @@ +/** + * @license + * Copyright 2020 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + sharedTestSetup, + sharedTestTeardown, +} from './test_helpers/setup_teardown.js'; + +suite('Workspace comment', function () { + setup(function () { + sharedTestSetup.call(this); + this.workspace = new Blockly.Workspace(); + }); + + teardown(function () { + sharedTestTeardown.call(this); + }); + + suite('getTopComments(ordered=true)', function () { + test('No comments', function () { + chai.assert.equal(this.workspace.getTopComments(true).length, 0); + }); + + test('One comment', function () { + const comment = new Blockly.WorkspaceComment( + this.workspace, + 'comment text', + 0, + 0, + 'comment id', + ); + chai.assert.equal(this.workspace.getTopComments(true).length, 1); + chai.assert.equal(this.workspace.commentDB.get('comment id'), comment); + }); + + test('After clear empty workspace', function () { + this.workspace.clear(); + chai.assert.equal(this.workspace.getTopComments(true).length, 0); + }); + + test('After clear non-empty workspace', function () { + new Blockly.WorkspaceComment( + this.workspace, + 'comment text', + 0, + 0, + 'comment id', + ); + this.workspace.clear(); + chai.assert.equal(this.workspace.getTopComments(true).length, 0); + chai.assert.isFalse(this.workspace.commentDB.has('comment id')); + }); + + test('After dispose', function () { + const comment = new Blockly.WorkspaceComment( + this.workspace, + 'comment text', + 0, + 0, + 'comment id', + ); + comment.dispose(); + chai.assert.equal(this.workspace.getTopComments(true).length, 0); + chai.assert.isFalse(this.workspace.commentDB.has('comment id')); + }); + }); + + suite('getTopComments(ordered=false)', function () { + test('No comments', function () { + chai.assert.equal(this.workspace.getTopComments(false).length, 0); + }); + + test('One comment', function () { + const comment = new Blockly.WorkspaceComment( + this.workspace, + 'comment text', + 0, + 0, + 'comment id', + ); + chai.assert.equal(this.workspace.getTopComments(false).length, 1); + chai.assert.equal(this.workspace.commentDB.get('comment id'), comment); + }); + + test('After clear empty workspace', function () { + this.workspace.clear(); + chai.assert.equal(this.workspace.getTopComments(false).length, 0); + }); + + test('After clear non-empty workspace', function () { + new Blockly.WorkspaceComment( + this.workspace, + 'comment text', + 0, + 0, + 'comment id', + ); + this.workspace.clear(); + chai.assert.equal(this.workspace.getTopComments(false).length, 0); + chai.assert.isFalse(this.workspace.commentDB.has('comment id')); + }); + + test('After dispose', function () { + const comment = new Blockly.WorkspaceComment( + this.workspace, + 'comment text', + 0, + 0, + 'comment id', + ); + comment.dispose(); + chai.assert.equal(this.workspace.getTopComments(false).length, 0); + chai.assert.isFalse(this.workspace.commentDB.has('comment id')); + }); + }); + + suite('getCommentById', function () { + test('Trivial', function () { + const comment = new Blockly.WorkspaceComment( + this.workspace, + 'comment text', + 0, + 0, + 'comment id', + ); + chai.assert.equal(this.workspace.getCommentById(comment.id), comment); + }); + + test('Null id', function () { + chai.assert.isNull(this.workspace.getCommentById(null)); + }); + + test('Non-existent id', function () { + chai.assert.isNull(this.workspace.getCommentById('badId')); + }); + + test('After dispose', function () { + const comment = new Blockly.WorkspaceComment( + this.workspace, + 'comment text', + 0, + 0, + 'comment id', + ); + comment.dispose(); + chai.assert.isNull(this.workspace.getCommentById(comment.id)); + }); + }); + + suite('dispose', function () { + test('Called twice', function () { + const comment = new Blockly.WorkspaceComment( + this.workspace, + 'comment text', + 0, + 0, + 'comment id', + ); + comment.dispose(); + // Nothing should go wrong the second time dispose is called. + comment.dispose(); + }); + }); + + suite('Width and height', function () { + setup(function () { + this.comment = new Blockly.WorkspaceComment( + this.workspace, + 'comment text', + 10, + 20, + 'comment id', + ); + }); + + test('Initial values', function () { + chai.assert.equal(this.comment.getWidth(), 20, 'Width'); + chai.assert.equal(this.comment.getHeight(), 10, 'Height'); + }); + + test('setWidth does not affect height', function () { + this.comment.setWidth(30); + chai.assert.equal(this.comment.getWidth(), 30, 'Width'); + chai.assert.equal(this.comment.getHeight(), 10, 'Height'); + }); + + test('setHeight does not affect width', function () { + this.comment.setHeight(30); + chai.assert.equal(this.comment.getWidth(), 20, 'Width'); + chai.assert.equal(this.comment.getHeight(), 30, 'Height'); + }); + }); + + suite('XY position', function () { + setup(function () { + this.comment = new Blockly.WorkspaceComment( + this.workspace, + 'comment text', + 10, + 20, + 'comment id', + ); + }); + + test('Initial position', function () { + const xy = this.comment.getRelativeToSurfaceXY(); + chai.assert.equal(xy.x, 0, 'Initial X position'); + chai.assert.equal(xy.y, 0, 'Initial Y position'); + }); + + test('moveBy', function () { + this.comment.moveBy(10, 100); + const xy = this.comment.getRelativeToSurfaceXY(); + chai.assert.equal(xy.x, 10, 'New X position'); + chai.assert.equal(xy.y, 100, 'New Y position'); + }); + }); + + suite('Content', function () { + setup(function () { + this.comment = new Blockly.WorkspaceComment( + this.workspace, + 'comment text', + 0, + 0, + 'comment id', + ); + }); + + teardown(function () { + sinon.restore(); + }); + + test('After creation', function () { + chai.assert.equal(this.comment.getContent(), 'comment text'); + chai.assert.equal( + this.workspace.undoStack_.length, + 1, + 'Workspace undo stack', + ); + }); + + test('Set to same value', function () { + this.comment.setContent('comment text'); + chai.assert.equal(this.comment.getContent(), 'comment text'); + // Setting the text to the old value does not fire an event. + chai.assert.equal( + this.workspace.undoStack_.length, + 1, + 'Workspace undo stack', + ); + }); + + test('Set to different value', function () { + this.comment.setContent('new comment text'); + chai.assert.equal(this.comment.getContent(), 'new comment text'); + chai.assert.equal( + this.workspace.undoStack_.length, + 2, + 'Workspace undo stack', + ); + }); + }); +}); diff --git a/tests/mocha/workspace_comment_test.js b/tests/mocha/workspace_comment_test.js index f2126dea2..6650848e5 100644 --- a/tests/mocha/workspace_comment_test.js +++ b/tests/mocha/workspace_comment_test.js @@ -1,6 +1,6 @@ /** * @license - * Copyright 2020 Google LLC + * Copyright 2024 Google LLC * SPDX-License-Identifier: Apache-2.0 */ @@ -12,256 +12,188 @@ import { suite('Workspace comment', function () { setup(function () { sharedTestSetup.call(this); - this.workspace = new Blockly.Workspace(); + this.workspace = new Blockly.inject('blocklyDiv', {}); + this.commentView = new Blockly.comments.CommentView(this.workspace); }); teardown(function () { sharedTestTeardown.call(this); }); - suite('getTopComments(ordered=true)', function () { - test('No comments', function () { - chai.assert.equal(this.workspace.getTopComments(true).length, 0); + suite('Listeners', function () { + suite('Text change listeners', function () { + test('text change listeners are called when text is changed', function () { + const spy = sinon.spy(); + this.commentView.addTextChangeListener(spy); + + this.commentView.setText('test'); + + chai.assert.isTrue( + spy.calledOnce, + 'Expected the spy to be called once', + ); + chai.assert.isTrue( + spy.calledWith('', 'test'), + 'Expected the spy to be called with the given args', + ); + }); + + test('text change listeners can remove themselves without skipping others', function () { + const fake1 = sinon.fake(); + const fake2 = sinon.fake(() => + this.commentView.removeTextChangeListener(fake2), + ); + const fake3 = sinon.fake(); + this.commentView.addTextChangeListener(fake1); + this.commentView.addTextChangeListener(fake2); + this.commentView.addTextChangeListener(fake3); + + this.commentView.setText('test'); + + chai.assert.isTrue( + fake1.calledOnce, + 'Expected the first listener to be called', + ); + chai.assert.isTrue( + fake2.calledOnce, + 'Expected the second listener to be called', + ); + chai.assert.isTrue( + fake3.calledOnce, + 'Expected the third listener to be called', + ); + }); }); - test('One comment', function () { - const comment = new Blockly.WorkspaceComment( - this.workspace, - 'comment text', - 0, - 0, - 'comment id', - ); - chai.assert.equal(this.workspace.getTopComments(true).length, 1); - chai.assert.equal(this.workspace.commentDB.get('comment id'), comment); + suite('Size change listeners', function () { + test('size change listeners are called when text is changed', function () { + const spy = sinon.spy(); + this.commentView.addSizeChangeListener(spy); + const originalSize = this.commentView.getSize(); + const newSize = new Blockly.utils.Size(1337, 1337); + + this.commentView.setSize(newSize); + + chai.assert.isTrue( + spy.calledOnce, + 'Expected the spy to be called once', + ); + chai.assert.isTrue( + spy.calledWith(originalSize, newSize), + 'Expected the spy to be called with the given args', + ); + }); + + test('size change listeners can remove themselves without skipping others', function () { + const fake1 = sinon.fake(); + const fake2 = sinon.fake(() => + this.commentView.removeSizeChangeListener(fake2), + ); + const fake3 = sinon.fake(); + this.commentView.addSizeChangeListener(fake1); + this.commentView.addSizeChangeListener(fake2); + this.commentView.addSizeChangeListener(fake3); + const newSize = new Blockly.utils.Size(1337, 1337); + + this.commentView.setSize(newSize); + + chai.assert.isTrue( + fake1.calledOnce, + 'Expected the first listener to be called', + ); + chai.assert.isTrue( + fake2.calledOnce, + 'Expected the second listener to be called', + ); + chai.assert.isTrue( + fake3.calledOnce, + 'Expected the third listener to be called', + ); + }); }); - test('After clear empty workspace', function () { - this.workspace.clear(); - chai.assert.equal(this.workspace.getTopComments(true).length, 0); + suite('Collapse change listeners', function () { + test('collapse change listeners are called when text is changed', function () { + const spy = sinon.spy(); + this.commentView.addOnCollapseListener(spy); + + this.commentView.setCollapsed(true); + + chai.assert.isTrue( + spy.calledOnce, + 'Expected the spy to be called once', + ); + chai.assert.isTrue( + spy.calledWith(true), + 'Expected the spy to be called with the given args', + ); + }); + + test('collapse change listeners can remove themselves without skipping others', function () { + const fake1 = sinon.fake(); + const fake2 = sinon.fake(() => + this.commentView.removeOnCollapseListener(fake2), + ); + const fake3 = sinon.fake(); + this.commentView.addOnCollapseListener(fake1); + this.commentView.addOnCollapseListener(fake2); + this.commentView.addOnCollapseListener(fake3); + + this.commentView.setCollapsed(true); + + chai.assert.isTrue( + fake1.calledOnce, + 'Expected the first listener to be called', + ); + chai.assert.isTrue( + fake2.calledOnce, + 'Expected the second listener to be called', + ); + chai.assert.isTrue( + fake3.calledOnce, + 'Expected the third listener to be called', + ); + }); }); - test('After clear non-empty workspace', function () { - new Blockly.WorkspaceComment( - this.workspace, - 'comment text', - 0, - 0, - 'comment id', - ); - this.workspace.clear(); - chai.assert.equal(this.workspace.getTopComments(true).length, 0); - chai.assert.isFalse(this.workspace.commentDB.has('comment id')); - }); + suite('Dispose change listeners', function () { + test('dispose listeners are called when text is changed', function () { + const spy = sinon.spy(); + this.commentView.addDisposeListener(spy); - test('After dispose', function () { - const comment = new Blockly.WorkspaceComment( - this.workspace, - 'comment text', - 0, - 0, - 'comment id', - ); - comment.dispose(); - chai.assert.equal(this.workspace.getTopComments(true).length, 0); - chai.assert.isFalse(this.workspace.commentDB.has('comment id')); - }); - }); + this.commentView.dispose(); - suite('getTopComments(ordered=false)', function () { - test('No comments', function () { - chai.assert.equal(this.workspace.getTopComments(false).length, 0); - }); + chai.assert.isTrue( + spy.calledOnce, + 'Expected the spy to be called once', + ); + }); - test('One comment', function () { - const comment = new Blockly.WorkspaceComment( - this.workspace, - 'comment text', - 0, - 0, - 'comment id', - ); - chai.assert.equal(this.workspace.getTopComments(false).length, 1); - chai.assert.equal(this.workspace.commentDB.get('comment id'), comment); - }); + test('dispose listeners can remove themselves without skipping others', function () { + const fake1 = sinon.fake(); + const fake2 = sinon.fake(() => + this.commentView.removeDisposeListener(fake2), + ); + const fake3 = sinon.fake(); + this.commentView.addDisposeListener(fake1); + this.commentView.addDisposeListener(fake2); + this.commentView.addDisposeListener(fake3); - test('After clear empty workspace', function () { - this.workspace.clear(); - chai.assert.equal(this.workspace.getTopComments(false).length, 0); - }); + this.commentView.dispose(); - test('After clear non-empty workspace', function () { - new Blockly.WorkspaceComment( - this.workspace, - 'comment text', - 0, - 0, - 'comment id', - ); - this.workspace.clear(); - chai.assert.equal(this.workspace.getTopComments(false).length, 0); - chai.assert.isFalse(this.workspace.commentDB.has('comment id')); - }); - - test('After dispose', function () { - const comment = new Blockly.WorkspaceComment( - this.workspace, - 'comment text', - 0, - 0, - 'comment id', - ); - comment.dispose(); - chai.assert.equal(this.workspace.getTopComments(false).length, 0); - chai.assert.isFalse(this.workspace.commentDB.has('comment id')); - }); - }); - - suite('getCommentById', function () { - test('Trivial', function () { - const comment = new Blockly.WorkspaceComment( - this.workspace, - 'comment text', - 0, - 0, - 'comment id', - ); - chai.assert.equal(this.workspace.getCommentById(comment.id), comment); - }); - - test('Null id', function () { - chai.assert.isNull(this.workspace.getCommentById(null)); - }); - - test('Non-existent id', function () { - chai.assert.isNull(this.workspace.getCommentById('badId')); - }); - - test('After dispose', function () { - const comment = new Blockly.WorkspaceComment( - this.workspace, - 'comment text', - 0, - 0, - 'comment id', - ); - comment.dispose(); - chai.assert.isNull(this.workspace.getCommentById(comment.id)); - }); - }); - - suite('dispose', function () { - test('Called twice', function () { - const comment = new Blockly.WorkspaceComment( - this.workspace, - 'comment text', - 0, - 0, - 'comment id', - ); - comment.dispose(); - // Nothing should go wrong the second time dispose is called. - comment.dispose(); - }); - }); - - suite('Width and height', function () { - setup(function () { - this.comment = new Blockly.WorkspaceComment( - this.workspace, - 'comment text', - 10, - 20, - 'comment id', - ); - }); - - test('Initial values', function () { - chai.assert.equal(this.comment.getWidth(), 20, 'Width'); - chai.assert.equal(this.comment.getHeight(), 10, 'Height'); - }); - - test('setWidth does not affect height', function () { - this.comment.setWidth(30); - chai.assert.equal(this.comment.getWidth(), 30, 'Width'); - chai.assert.equal(this.comment.getHeight(), 10, 'Height'); - }); - - test('setHeight does not affect width', function () { - this.comment.setHeight(30); - chai.assert.equal(this.comment.getWidth(), 20, 'Width'); - chai.assert.equal(this.comment.getHeight(), 30, 'Height'); - }); - }); - - suite('XY position', function () { - setup(function () { - this.comment = new Blockly.WorkspaceComment( - this.workspace, - 'comment text', - 10, - 20, - 'comment id', - ); - }); - - test('Initial position', function () { - const xy = this.comment.getRelativeToSurfaceXY(); - chai.assert.equal(xy.x, 0, 'Initial X position'); - chai.assert.equal(xy.y, 0, 'Initial Y position'); - }); - - test('moveBy', function () { - this.comment.moveBy(10, 100); - const xy = this.comment.getRelativeToSurfaceXY(); - chai.assert.equal(xy.x, 10, 'New X position'); - chai.assert.equal(xy.y, 100, 'New Y position'); - }); - }); - - suite('Content', function () { - setup(function () { - this.comment = new Blockly.WorkspaceComment( - this.workspace, - 'comment text', - 0, - 0, - 'comment id', - ); - }); - - teardown(function () { - sinon.restore(); - }); - - test('After creation', function () { - chai.assert.equal(this.comment.getContent(), 'comment text'); - chai.assert.equal( - this.workspace.undoStack_.length, - 1, - 'Workspace undo stack', - ); - }); - - test('Set to same value', function () { - this.comment.setContent('comment text'); - chai.assert.equal(this.comment.getContent(), 'comment text'); - // Setting the text to the old value does not fire an event. - chai.assert.equal( - this.workspace.undoStack_.length, - 1, - 'Workspace undo stack', - ); - }); - - test('Set to different value', function () { - this.comment.setContent('new comment text'); - chai.assert.equal(this.comment.getContent(), 'new comment text'); - chai.assert.equal( - this.workspace.undoStack_.length, - 2, - 'Workspace undo stack', - ); + chai.assert.isTrue( + fake1.calledOnce, + 'Expected the first listener to be called', + ); + chai.assert.isTrue( + fake2.calledOnce, + 'Expected the second listener to be called', + ); + chai.assert.isTrue( + fake3.calledOnce, + 'Expected the third listener to be called', + ); + }); }); }); }); From 30127db35fb500a771d05464b4095ed3191f7344 Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Mon, 11 Mar 2024 20:57:47 +0000 Subject: [PATCH 19/92] fix: comment min size (#7915) * feat: add min and max to size * fix: properly calculate the minimum size of the comment * fix: inline docs --- core/comments/comment_view.ts | 83 ++++++++++++++++++++++++++++------- core/utils/size.ts | 16 +++++++ 2 files changed, 84 insertions(+), 15 deletions(-) diff --git a/core/comments/comment_view.ts b/core/comments/comment_view.ts index 21d2ebcfd..05491f0ac 100644 --- a/core/comments/comment_view.ts +++ b/core/comments/comment_view.ts @@ -13,7 +13,6 @@ import * as css from '../css.js'; import {Coordinate, Size, browserEvents} from '../utils.js'; import * as touch from '../touch.js'; -const MIN_SIZE = new Size(100, 60); export class CommentView implements IRenderedElement { /** The root group element of the comment view. */ private svgRoot: SVGGElement; @@ -257,19 +256,19 @@ export class CommentView implements IRenderedElement { * elements to reflect the new size. */ setSize(size: Size) { - size = new Size( - Math.max(size.width, MIN_SIZE.width), - Math.max(size.height, MIN_SIZE.height), - ); - - const oldSize = this.size; - this.size = size; const topBarSize = this.topBar.getBBox(); const deleteSize = this.deleteIcon.getBBox(); const foldoutSize = this.foldoutIcon.getBBox(); const textPreviewSize = this.textPreview.getBBox(); const resizeSize = this.resizeHandle.getBBox(); + size = Size.max( + size, + this.calcMinSize(topBarSize, foldoutSize, deleteSize), + ); + const oldSize = this.size; + this.size = size; + this.svgRoot.setAttribute('height', `${size.height}`); this.svgRoot.setAttribute('width', `${size.width}`); @@ -292,6 +291,53 @@ export class CommentView implements IRenderedElement { this.onSizeChange(oldSize, this.size); } + /** + * Calculates the minimum size for the uncollapsed comment based on text + * size and visible icons. + * + * The minimum width is based on the width of the truncated preview text. + * + * The minimum height is based on the height of the top bar. + */ + private calcMinSize( + topBarSize: Size, + foldoutSize: Size, + deleteSize: Size, + ): Size { + this.updateTextPreview(this.textArea.value ?? ''); + const textPreviewWidth = dom.getTextWidth(this.textPreview); + + const foldoutMargin = this.calcFoldoutMargin(topBarSize, foldoutSize); + const deleteMargin = this.calcDeleteMargin(topBarSize, deleteSize); + + let width = textPreviewWidth; + if (this.foldoutIcon.checkVisibility()) { + width += foldoutSize.width + foldoutMargin * 2; + } else if (textPreviewWidth) { + width += 4; // Arbitrary margin before text. + } + if (this.deleteIcon.checkVisibility()) { + width += deleteSize.width + deleteMargin * 2; + } else if (textPreviewWidth) { + width += 4; // Arbitrary margin after text. + } + + // Arbitrary additional height. + const height = topBarSize.height + 20; + + return new Size(width, height); + } + + /** Calculates the margin that should exist around the delete icon. */ + private calcDeleteMargin(topBarSize: Size, deleteSize: Size) { + return (topBarSize.height - deleteSize.height) / 2; + } + + /** Calculates the margin that should exist around the foldout icon. */ + private calcFoldoutMargin(topBarSize: Size, foldoutSize: Size) { + return (topBarSize.height - foldoutSize.height) / 2; + } + /** Updates the size of the text area elements to reflect the new size. */ private updateTextAreaSize(size: Size, topBarSize: Size) { this.foreignObject.setAttribute( @@ -313,7 +359,7 @@ export class CommentView implements IRenderedElement { topBarSize: Size, deleteSize: Size, ) { - const deleteMargin = (topBarSize.height - deleteSize.height) / 2; + const deleteMargin = this.calcDeleteMargin(topBarSize, deleteSize); this.deleteIcon.setAttribute('y', `${deleteMargin}`); this.deleteIcon.setAttribute( 'x', @@ -325,7 +371,7 @@ export class CommentView implements IRenderedElement { * Updates the position of the foldout icon elements to reflect the new size. */ private updateFoldoutIconPosition(topBarSize: Size, foldoutSize: Size) { - const foldoutMargin = (topBarSize.height - foldoutSize.height) / 2; + const foldoutMargin = this.calcFoldoutMargin(topBarSize, foldoutSize); this.foldoutIcon.setAttribute('y', `${foldoutMargin}`); this.foldoutIcon.setAttribute('x', `${foldoutMargin}`); } @@ -341,8 +387,8 @@ export class CommentView implements IRenderedElement { foldoutSize: Size, ) { const textPreviewMargin = (topBarSize.height - textPreviewSize.height) / 2; - const deleteMargin = (topBarSize.height - deleteSize.height) / 2; - const foldoutMargin = (topBarSize.height - foldoutSize.height) / 2; + const deleteMargin = this.calcDeleteMargin(topBarSize, deleteSize); + const foldoutMargin = this.calcFoldoutMargin(topBarSize, foldoutSize); const textPreviewWidth = size.width - @@ -578,13 +624,20 @@ export class CommentView implements IRenderedElement { private onTextChange() { const oldText = this.text; this.text = this.textArea.value; - this.textPreviewNode.textContent = this.truncateText(this.text); + this.updateTextPreview(this.text); + // Update size in case our minimum size increased. + this.setSize(this.size); // Loop through listeners backwards in case they remove themselves. for (let i = this.textChangeListeners.length - 1; i >= 0; i--) { this.textChangeListeners[i](oldText, this.text); } } + /** Updates the preview text element to reflect the given text. */ + private updateTextPreview(text: string) { + this.textPreviewNode.textContent = this.truncateText(text); + } + /** Truncates the text to fit within the top view. */ private truncateText(text: string): string { return text.length >= 12 ? `${text.substring(0, 9)}...` : text; @@ -703,11 +756,11 @@ css.register(` .blocklyComment .blocklyCommentPreview.blocklyText { fill: var(--commentIconColour); dominant-baseline: middle; - display: none; + visibility: hidden; } .blocklyCollapsed.blocklyComment .blocklyCommentPreview { - display: block; + visibility: visible; } .blocklyCollapsed.blocklyComment .blocklyCommentForeignObject, diff --git a/core/utils/size.ts b/core/utils/size.ts index ab88d2c84..705dc2c28 100644 --- a/core/utils/size.ts +++ b/core/utils/size.ts @@ -43,4 +43,20 @@ export class Size { } return a.width === b.width && a.height === b.height; } + + /** + * Returns a new size with the maximum width and height values out of both + * sizes. + */ + static max(a: Size, b: Size): Size { + return new Size(Math.max(a.width, b.width), Math.max(a.height, b.height)); + } + + /** + * Returns a new size with the minimum width and height values out of both + * sizes. + */ + static min(a: Size, b: Size): Size { + return new Size(Math.min(a.width, b.width), Math.min(a.height, b.height)); + } } From a5126d117625b29b68ad18751105fa65963fffa1 Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Wed, 13 Mar 2024 16:23:11 +0000 Subject: [PATCH 20/92] fix!: have disposing be true from start of dispose (#7891) --- core/block.ts | 8 +++----- core/block_svg.ts | 4 ++-- core/connection.ts | 9 ++++++--- core/xml.ts | 6 +++--- tests/mocha/block_test.js | 22 +++++++++++++++++----- 5 files changed, 31 insertions(+), 18 deletions(-) diff --git a/core/block.ts b/core/block.ts index ea7b63a7a..394459e92 100644 --- a/core/block.ts +++ b/core/block.ts @@ -189,7 +189,7 @@ export class Block implements IASTNodeLocation, IDeletable { /** * Is the current block currently in the process of being disposed? */ - private disposing = false; + protected disposing = false; /** * Has this block been fully initialized? E.g. all fields initailized. @@ -319,7 +319,7 @@ export class Block implements IASTNodeLocation, IDeletable { * children of this block. */ dispose(healStack: boolean) { - if (this.isDeadOrDying()) return; + this.disposing = true; // Dispose of this change listener before unplugging. // Technically not necessary due to the event firing delay. @@ -342,15 +342,13 @@ export class Block implements IASTNodeLocation, IDeletable { * E.g. does not fire events, unplug the block, etc. */ protected disposeInternal() { - if (this.isDeadOrDying()) return; - + this.disposing = true; if (this.onchangeWrapper_) { this.workspace.removeChangeListener(this.onchangeWrapper_); } this.workspace.removeTypedBlock(this); this.workspace.removeBlockById(this.id); - this.disposing = true; if (typeof this.destroy === 'function') this.destroy(); diff --git a/core/block_svg.ts b/core/block_svg.ts index 87a8cc6c5..f276d1e8e 100644 --- a/core/block_svg.ts +++ b/core/block_svg.ts @@ -776,7 +776,7 @@ export class BlockSvg * @param animate If true, show a disposal animation and sound. */ override dispose(healStack?: boolean, animate?: boolean) { - if (this.isDeadOrDying()) return; + this.disposing = true; Tooltip.dispose(); ContextMenu.hide(); @@ -795,7 +795,7 @@ export class BlockSvg * E.g. does trigger UI effects, remove nodes, etc. */ override disposeInternal() { - if (this.isDeadOrDying()) return; + this.disposing = true; super.disposeInternal(); if (common.getSelected() === this) { diff --git a/core/connection.ts b/core/connection.ts index 56ba4c3a5..56bf4c3e7 100644 --- a/core/connection.ts +++ b/core/connection.ts @@ -153,8 +153,10 @@ export class Connection implements IASTNodeLocationWithBlock { dispose() { // isConnected returns true for shadows and non-shadows. if (this.isConnected()) { - // Destroy the attached shadow block & its children (if it exists). - this.setShadowStateInternal(); + if (this.isSuperior()) { + // Destroy the attached shadow block & its children (if it exists). + this.setShadowStateInternal(); + } const targetBlock = this.targetBlock(); if (targetBlock && !targetBlock.isDeadOrDying()) { @@ -600,6 +602,8 @@ export class Connection implements IASTNodeLocationWithBlock { this.shadowDom = shadowDom; this.shadowState = shadowState; + if (this.getSourceBlock().isDeadOrDying()) return; + const target = this.targetBlock(); if (!target) { this.respawnShadow_(); @@ -608,7 +612,6 @@ export class Connection implements IASTNodeLocationWithBlock { } } else if (target.isShadow()) { target.dispose(false); - if (this.getSourceBlock().isDeadOrDying()) return; this.respawnShadow_(); if (this.targetBlock() && this.targetBlock()!.isShadow()) { this.serializeShadow(this.targetBlock()); diff --git a/core/xml.ts b/core/xml.ts index ee72f526b..0f2c4f143 100644 --- a/core/xml.ts +++ b/core/xml.ts @@ -250,13 +250,13 @@ export function blockToDom( if (!block.isEnabled()) { element.setAttribute('disabled', 'true'); } - if (!block.isDeletable() && !block.isShadow()) { + if (!block.isOwnDeletable()) { element.setAttribute('deletable', 'false'); } - if (!block.isMovable() && !block.isShadow()) { + if (!block.isOwnMovable()) { element.setAttribute('movable', 'false'); } - if (!block.isEditable()) { + if (!block.isOwnEditable()) { element.setAttribute('editable', 'false'); } diff --git a/tests/mocha/block_test.js b/tests/mocha/block_test.js index f5b2e6a82..c57ccbb15 100644 --- a/tests/mocha/block_test.js +++ b/tests/mocha/block_test.js @@ -67,9 +67,9 @@ suite('Blocks', function () { function createTestBlocks(workspace, isRow) { const blockType = isRow ? 'row_block' : 'stack_block'; - const blockA = workspace.newBlock(blockType); - const blockB = workspace.newBlock(blockType); - const blockC = workspace.newBlock(blockType); + const blockA = workspace.newBlock(blockType, 'a'); + const blockB = workspace.newBlock(blockType, 'b'); + const blockC = workspace.newBlock(blockType, 'c'); if (isRow) { blockA.inputList[0].connection.connect(blockB.outputConnection); @@ -386,8 +386,14 @@ suite('Blocks', function () { test('Child is shadow', function () { const blocks = this.blocks; - blocks.C.setShadow(true); + blocks.C.dispose(); + blocks.B.inputList[0].connection.setShadowState({ + 'type': 'row_block', + 'id': 'c', + }); + blocks.B.dispose(true); + // Even though we're asking to heal, it will appear as if it has not // healed because shadows always get destroyed. assertDisposedNoheal(blocks); @@ -423,8 +429,14 @@ suite('Blocks', function () { test('Child is shadow', function () { const blocks = this.blocks; - blocks.C.setShadow(true); + blocks.C.dispose(); + blocks.B.nextConnection.setShadowState({ + 'type': 'stack_block', + 'id': 'c', + }); + blocks.B.dispose(true); + // Even though we're asking to heal, it will appear as if it has not // healed because shadows always get destroyed. assertDisposedNoheal(blocks); From 67c3aae76c6a8a232bc24bc1c469c956ed5a7582 Mon Sep 17 00:00:00 2001 From: Rachel Fenichel Date: Wed, 13 Mar 2024 13:28:26 -0700 Subject: [PATCH 21/92] feat!: delete angle, colour, and multiline input fields and blocks (#7932) * chore: delete mocha tests for angle field * feat! : delete angle field * chore(tests): delete colour tests from generator tests and golden files * chore: delete colour blocks and associated generators * chore: remove colour blocks from playgrounds * chore: delete mocha tests for colour fields * chore: fix incorrect comment * chore: delete colour field from core * chore: delete multiline input tests from generators tests and golden files * chore: delete multiline text block and associated generators * chore: remove multiline text block from playgrounds * chore: delete mocha tests for multiline input field * chore: delete multiline input field from core --- blocks/blocks.ts | 3 - blocks/colour.ts | 112 ---- blocks/text.ts | 33 -- core/blockly.ts | 36 -- core/field_angle.ts | 612 ------------------- core/field_colour.ts | 721 ----------------------- core/field_image.ts | 2 +- core/field_multilineinput.ts | 526 ----------------- generators/dart.ts | 2 - generators/dart/colour.ts | 132 ----- generators/dart/text.ts | 10 - generators/javascript.ts | 2 - generators/javascript/colour.ts | 101 ---- generators/javascript/text.ts | 10 - generators/lua.ts | 2 - generators/lua/colour.ts | 89 --- generators/lua/text.ts | 10 - generators/php.ts | 2 - generators/php/colour.ts | 102 ---- generators/php/text.ts | 10 - generators/python.ts | 2 - generators/python/colour.ts | 88 --- generators/python/text.ts | 10 - tests/generators/colour.xml | 318 ---------- tests/generators/golden/generated.dart | 100 +--- tests/generators/golden/generated.js | 85 +-- tests/generators/golden/generated.lua | 77 --- tests/generators/golden/generated.php | 251 +++----- tests/generators/golden/generated.py | 228 +++---- tests/generators/index.html | 8 - tests/generators/text.xml | 94 --- tests/mocha/field_angle_test.js | 389 ------------ tests/mocha/field_multilineinput_test.js | 284 --------- tests/mocha/index.html | 3 - tests/mocha/serializer_test.js | 218 ------- tests/mocha/xml_test.js | 42 -- tests/multi_playground.html | 39 -- tests/playground.html | 40 -- 38 files changed, 164 insertions(+), 4629 deletions(-) delete mode 100644 blocks/colour.ts delete mode 100644 core/field_angle.ts delete mode 100644 core/field_colour.ts delete mode 100644 core/field_multilineinput.ts delete mode 100644 generators/dart/colour.ts delete mode 100644 generators/javascript/colour.ts delete mode 100644 generators/lua/colour.ts delete mode 100644 generators/php/colour.ts delete mode 100644 generators/python/colour.ts delete mode 100644 tests/generators/colour.xml delete mode 100644 tests/mocha/field_angle_test.js delete mode 100644 tests/mocha/field_multilineinput_test.js diff --git a/blocks/blocks.ts b/blocks/blocks.ts index f4a9936ed..a9874e54d 100644 --- a/blocks/blocks.ts +++ b/blocks/blocks.ts @@ -6,7 +6,6 @@ // Former goog.module ID: Blockly.libraryBlocks -import * as colour from './colour.js'; import * as lists from './lists.js'; import * as logic from './logic.js'; import * as loops from './loops.js'; @@ -18,7 +17,6 @@ import * as variablesDynamic from './variables_dynamic.js'; import type {BlockDefinition} from '../core/blocks.js'; export { - colour, lists, logic, loops, @@ -35,7 +33,6 @@ export { */ export const blocks: {[key: string]: BlockDefinition} = Object.assign( {}, - colour.blocks, lists.blocks, logic.blocks, loops.blocks, diff --git a/blocks/colour.ts b/blocks/colour.ts deleted file mode 100644 index e57e4ba9b..000000000 --- a/blocks/colour.ts +++ /dev/null @@ -1,112 +0,0 @@ -/** - * @license - * Copyright 2012 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -// Former goog.module ID: Blockly.libraryBlocks.colour - -import { - createBlockDefinitionsFromJsonArray, - defineBlocks, -} from '../core/common.js'; -import '../core/field_colour.js'; - -/** - * A dictionary of the block definitions provided by this module. - */ -export const blocks = createBlockDefinitionsFromJsonArray([ - // Block for colour picker. - { - 'type': 'colour_picker', - 'message0': '%1', - 'args0': [ - { - 'type': 'field_colour', - 'name': 'COLOUR', - 'colour': '#ff0000', - }, - ], - 'output': 'Colour', - 'helpUrl': '%{BKY_COLOUR_PICKER_HELPURL}', - 'style': 'colour_blocks', - 'tooltip': '%{BKY_COLOUR_PICKER_TOOLTIP}', - 'extensions': ['parent_tooltip_when_inline'], - }, - - // Block for random colour. - { - 'type': 'colour_random', - 'message0': '%{BKY_COLOUR_RANDOM_TITLE}', - 'output': 'Colour', - 'helpUrl': '%{BKY_COLOUR_RANDOM_HELPURL}', - 'style': 'colour_blocks', - 'tooltip': '%{BKY_COLOUR_RANDOM_TOOLTIP}', - }, - - // Block for composing a colour from RGB components. - { - 'type': 'colour_rgb', - 'message0': - '%{BKY_COLOUR_RGB_TITLE} %{BKY_COLOUR_RGB_RED} %1 %{BKY_COLOUR_RGB_GREEN} %2 %{BKY_COLOUR_RGB_BLUE} %3', - 'args0': [ - { - 'type': 'input_value', - 'name': 'RED', - 'check': 'Number', - 'align': 'RIGHT', - }, - { - 'type': 'input_value', - 'name': 'GREEN', - 'check': 'Number', - 'align': 'RIGHT', - }, - { - 'type': 'input_value', - 'name': 'BLUE', - 'check': 'Number', - 'align': 'RIGHT', - }, - ], - 'output': 'Colour', - 'helpUrl': '%{BKY_COLOUR_RGB_HELPURL}', - 'style': 'colour_blocks', - 'tooltip': '%{BKY_COLOUR_RGB_TOOLTIP}', - }, - - // Block for blending two colours together. - { - 'type': 'colour_blend', - 'message0': - '%{BKY_COLOUR_BLEND_TITLE} %{BKY_COLOUR_BLEND_COLOUR1} ' + - '%1 %{BKY_COLOUR_BLEND_COLOUR2} %2 %{BKY_COLOUR_BLEND_RATIO} %3', - 'args0': [ - { - 'type': 'input_value', - 'name': 'COLOUR1', - 'check': 'Colour', - 'align': 'RIGHT', - }, - { - 'type': 'input_value', - 'name': 'COLOUR2', - 'check': 'Colour', - 'align': 'RIGHT', - }, - { - 'type': 'input_value', - 'name': 'RATIO', - 'check': 'Number', - 'align': 'RIGHT', - }, - ], - 'output': 'Colour', - 'helpUrl': '%{BKY_COLOUR_BLEND_HELPURL}', - 'style': 'colour_blocks', - 'tooltip': '%{BKY_COLOUR_BLEND_TOOLTIP}', - }, -]); - -// Register provided blocks. -defineBlocks(blocks); diff --git a/blocks/text.ts b/blocks/text.ts index 824da782b..e0470c169 100644 --- a/blocks/text.ts +++ b/blocks/text.ts @@ -23,7 +23,6 @@ import { createBlockDefinitionsFromJsonArray, defineBlocks, } from '../core/common.js'; -import '../core/field_multilineinput.js'; import '../core/field_variable.js'; import {ValueInput} from '../core/inputs/value_input.js'; @@ -48,38 +47,6 @@ export const blocks = createBlockDefinitionsFromJsonArray([ 'tooltip': '%{BKY_TEXT_TEXT_TOOLTIP}', 'extensions': ['text_quotes', 'parent_tooltip_when_inline'], }, - { - 'type': 'text_multiline', - 'message0': '%1 %2', - 'args0': [ - { - 'type': 'field_image', - 'src': - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAARCAYAAADpP' + - 'U2iAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAdhgAAHYYBXaITgQAAABh0RVh0' + - 'U29mdHdhcmUAcGFpbnQubmV0IDQuMS42/U4J6AAAAP1JREFUOE+Vks0KQUEYhjm' + - 'RIja4ABtZ2dm5A3t3Ia6AUm7CylYuQRaUhZSlLZJiQbFAyRnPN33y01HOW08z88' + - '73zpwzM4F3GWOCruvGIE4/rLaV+Nq1hVGMBqzhqlxgCys4wJA65xnogMHsQ5luj' + - 'nYHTejBBCK2mE4abjCgMGhNxHgDFWjDSG07kdfVa2pZMf4ZyMAdWmpZMfYOsLiD' + - 'MYMjlMB+K613QISRhTnITnsYg5yUd0DETmEoMlkFOeIT/A58iyK5E18BuTBfgYX' + - 'fwNJv4P9/oEBerLylOnRhygmGdPpTTBZAPkde61lbQe4moWUvYUZYLfUNftIY4z' + - 'wA5X2Z9AYnQrEAAAAASUVORK5CYII=', - 'width': 12, - 'height': 17, - 'alt': '\u00B6', - }, - { - 'type': 'field_multilinetext', - 'name': 'TEXT', - 'text': '', - }, - ], - 'output': 'String', - 'style': 'text_blocks', - 'helpUrl': '%{BKY_TEXT_TEXT_HELPURL}', - 'tooltip': '%{BKY_TEXT_TEXT_TOOLTIP}', - 'extensions': ['parent_tooltip_when_inline'], - }, { 'type': 'text_join', 'message0': '', diff --git a/core/blockly.ts b/core/blockly.ts index e68199854..e66f5e4eb 100644 --- a/core/blockly.ts +++ b/core/blockly.ts @@ -50,24 +50,12 @@ import { FieldValidator, UnattachedFieldError, } from './field.js'; -import { - FieldAngle, - FieldAngleConfig, - FieldAngleFromJsonConfig, - FieldAngleValidator, -} from './field_angle.js'; import { FieldCheckbox, FieldCheckboxConfig, FieldCheckboxFromJsonConfig, FieldCheckboxValidator, } from './field_checkbox.js'; -import { - FieldColour, - FieldColourConfig, - FieldColourFromJsonConfig, - FieldColourValidator, -} from './field_colour.js'; import { FieldDropdown, FieldDropdownConfig, @@ -88,12 +76,6 @@ import { FieldLabelFromJsonConfig, } from './field_label.js'; import {FieldLabelSerializable} from './field_label_serializable.js'; -import { - FieldMultilineInput, - FieldMultilineInputConfig, - FieldMultilineInputFromJsonConfig, - FieldMultilineInputValidator, -} from './field_multilineinput.js'; import { FieldNumber, FieldNumberConfig, @@ -487,24 +469,12 @@ export {DeleteArea}; export {DragTarget}; export const DropDownDiv = dropDownDiv; export {Field, FieldConfig, FieldValidator, UnattachedFieldError}; -export { - FieldAngle, - FieldAngleConfig, - FieldAngleFromJsonConfig, - FieldAngleValidator, -}; export { FieldCheckbox, FieldCheckboxConfig, FieldCheckboxFromJsonConfig, FieldCheckboxValidator, }; -export { - FieldColour, - FieldColourConfig, - FieldColourFromJsonConfig, - FieldColourValidator, -}; export { FieldDropdown, FieldDropdownConfig, @@ -517,12 +487,6 @@ export { export {FieldImage, FieldImageConfig, FieldImageFromJsonConfig}; export {FieldLabel, FieldLabelConfig, FieldLabelFromJsonConfig}; export {FieldLabelSerializable}; -export { - FieldMultilineInput, - FieldMultilineInputConfig, - FieldMultilineInputFromJsonConfig, - FieldMultilineInputValidator, -}; export { FieldNumber, FieldNumberConfig, diff --git a/core/field_angle.ts b/core/field_angle.ts deleted file mode 100644 index e65ef16a9..000000000 --- a/core/field_angle.ts +++ /dev/null @@ -1,612 +0,0 @@ -/** - * @license - * Copyright 2013 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * Angle input field. - * - * @class - */ -// Former goog.module ID: Blockly.FieldAngle - -import {BlockSvg} from './block_svg.js'; -import * as browserEvents from './browser_events.js'; -import * as Css from './css.js'; -import * as dropDownDiv from './dropdowndiv.js'; -import * as eventUtils from './events/utils.js'; -import {Field, UnattachedFieldError} from './field.js'; -import * as fieldRegistry from './field_registry.js'; -import { - FieldInput, - FieldInputConfig, - FieldInputValidator, -} from './field_input.js'; -import * as dom from './utils/dom.js'; -import * as math from './utils/math.js'; -import {Svg} from './utils/svg.js'; -import * as userAgent from './utils/useragent.js'; -import * as WidgetDiv from './widgetdiv.js'; - -/** - * Class for an editable angle field. - */ -export class FieldAngle extends FieldInput { - /** Half the width of protractor image. */ - static readonly HALF = 100 / 2; - - /** - * Radius of protractor circle. Slightly smaller than protractor size since - * otherwise SVG crops off half the border at the edges. - */ - static readonly RADIUS: number = FieldAngle.HALF - 1; - - /** - * Default property describing which direction makes an angle field's value - * increase. Angle increases clockwise (true) or counterclockwise (false). - */ - static readonly CLOCKWISE = false; - - /** - * The default offset of 0 degrees (and all angles). Always offsets in the - * counterclockwise direction, regardless of the field's clockwise property. - * Usually either 0 (0 = right) or 90 (0 = up). - */ - static readonly OFFSET = 0; - - /** - * The default maximum angle to allow before wrapping. - * Usually either 360 (for 0 to 359.9) or 180 (for -179.9 to 180). - */ - static readonly WRAP = 360; - - /** - * The default amount to round angles to when using a mouse or keyboard nav - * input. Must be a positive integer to support keyboard navigation. - */ - static readonly ROUND = 15; - - /** - * Whether the angle should increase as the angle picker is moved clockwise - * (true) or counterclockwise (false). - */ - private clockwise = FieldAngle.CLOCKWISE; - - /** - * The offset of zero degrees (and all other angles). - */ - private offset = FieldAngle.OFFSET; - - /** - * The maximum angle to allow before wrapping. - */ - private wrap = FieldAngle.WRAP; - - /** - * The amount to round angles to when using a mouse or keyboard nav input. - */ - private round = FieldAngle.ROUND; - - /** - * Array holding info needed to unbind events. - * Used for disposing. - * Ex: [[node, name, func], [node, name, func]]. - */ - private boundEvents: browserEvents.Data[] = []; - - /** Dynamic red line pointing at the value's angle. */ - private line: SVGLineElement | null = null; - - /** Dynamic pink area extending from 0 to the value's angle. */ - private gauge: SVGPathElement | null = null; - - /** The degree symbol for this field. */ - protected symbol_: SVGTSpanElement | null = null; - - /** - * @param value The initial value of the field. Should cast to a number. - * Defaults to 0. Also accepts Field.SKIP_SETUP if you wish to skip setup - * (only used by subclasses that want to handle configuration and setting - * the field value after their own constructors have run). - * @param validator A function that is called to validate changes to the - * field's value. Takes in a number & returns a validated number, or null - * to abort the change. - * @param config A map of options used to configure the field. - * See the [field creation documentation]{@link - * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/angle#creation} - * for a list of properties this parameter supports. - */ - constructor( - value?: string | number | typeof Field.SKIP_SETUP, - validator?: FieldAngleValidator, - config?: FieldAngleConfig, - ) { - super(Field.SKIP_SETUP); - - if (value === Field.SKIP_SETUP) return; - if (config) { - this.configure_(config); - } - this.setValue(value); - if (validator) { - this.setValidator(validator); - } - } - - /** - * Configure the field based on the given map of options. - * - * @param config A map of options to configure the field based on. - */ - protected override configure_(config: FieldAngleConfig) { - super.configure_(config); - - switch (config.mode) { - case Mode.COMPASS: - this.clockwise = true; - this.offset = 90; - break; - case Mode.PROTRACTOR: - // This is the default mode, so we could do nothing. But just to - // future-proof, we'll set it anyway. - this.clockwise = false; - this.offset = 0; - break; - } - - // Allow individual settings to override the mode setting. - if (config.clockwise) this.clockwise = config.clockwise; - if (config.offset) this.offset = config.offset; - if (config.wrap) this.wrap = config.wrap; - if (config.round) this.round = config.round; - } - - /** - * Create the block UI for this field. - */ - override initView() { - super.initView(); - // Add the degree symbol to the left of the number, - // even in RTL (issue #2380). - this.symbol_ = dom.createSvgElement(Svg.TSPAN, {}); - this.symbol_.appendChild(document.createTextNode('°')); - this.getTextElement().appendChild(this.symbol_); - } - - /** Updates the angle when the field rerenders. */ - protected override render_() { - super.render_(); - this.updateGraph(); - } - - /** - * Create and show the angle field's editor. - * - * @param e Optional mouse event that triggered the field to open, - * or undefined if triggered programmatically. - */ - protected override showEditor_(e?: Event) { - // Mobile browsers have issues with in-line textareas (focus & keyboards). - const noFocus = userAgent.MOBILE || userAgent.ANDROID || userAgent.IPAD; - super.showEditor_(e, noFocus); - - const editor = this.dropdownCreate(); - dropDownDiv.getContentDiv().appendChild(editor); - - if (this.sourceBlock_ instanceof BlockSvg) { - dropDownDiv.setColour( - this.sourceBlock_.style.colourPrimary, - this.sourceBlock_.style.colourTertiary, - ); - } - - dropDownDiv.showPositionedByField(this, this.dropdownDispose.bind(this)); - - this.updateGraph(); - } - - /** - * Creates the angle dropdown editor. - * - * @returns The newly created slider. - */ - private dropdownCreate(): SVGSVGElement { - const svg = dom.createSvgElement(Svg.SVG, { - 'xmlns': dom.SVG_NS, - 'xmlns:html': dom.HTML_NS, - 'xmlns:xlink': dom.XLINK_NS, - 'version': '1.1', - 'height': FieldAngle.HALF * 2 + 'px', - 'width': FieldAngle.HALF * 2 + 'px', - }); - svg.style.touchAction = 'none'; - const circle = dom.createSvgElement( - Svg.CIRCLE, - { - 'cx': FieldAngle.HALF, - 'cy': FieldAngle.HALF, - 'r': FieldAngle.RADIUS, - 'class': 'blocklyAngleCircle', - }, - svg, - ); - this.gauge = dom.createSvgElement( - Svg.PATH, - {'class': 'blocklyAngleGauge'}, - svg, - ); - this.line = dom.createSvgElement( - Svg.LINE, - { - 'x1': FieldAngle.HALF, - 'y1': FieldAngle.HALF, - 'class': 'blocklyAngleLine', - }, - svg, - ); - // Draw markers around the edge. - for (let angle = 0; angle < 360; angle += 15) { - dom.createSvgElement( - Svg.LINE, - { - 'x1': FieldAngle.HALF + FieldAngle.RADIUS, - 'y1': FieldAngle.HALF, - 'x2': - FieldAngle.HALF + FieldAngle.RADIUS - (angle % 45 === 0 ? 10 : 5), - 'y2': FieldAngle.HALF, - 'class': 'blocklyAngleMarks', - 'transform': - 'rotate(' + - angle + - ',' + - FieldAngle.HALF + - ',' + - FieldAngle.HALF + - ')', - }, - svg, - ); - } - - // The angle picker is different from other fields in that it updates on - // mousemove even if it's not in the middle of a drag. In future we may - // change this behaviour. - this.boundEvents.push( - browserEvents.conditionalBind(svg, 'click', this, this.hide), - ); - // On touch devices, the picker's value is only updated with a drag. Add - // a click handler on the drag surface to update the value if the surface - // is clicked. - this.boundEvents.push( - browserEvents.conditionalBind( - circle, - 'pointerdown', - this, - this.onMouseMove_, - true, - ), - ); - this.boundEvents.push( - browserEvents.conditionalBind( - circle, - 'pointermove', - this, - this.onMouseMove_, - true, - ), - ); - return svg; - } - - /** Disposes of events and DOM-references belonging to the angle editor. */ - private dropdownDispose() { - for (const event of this.boundEvents) { - browserEvents.unbind(event); - } - this.boundEvents.length = 0; - this.gauge = null; - this.line = null; - } - - /** Hide the editor. */ - private hide() { - dropDownDiv.hideIfOwner(this); - WidgetDiv.hide(); - } - - /** - * Set the angle to match the mouse's position. - * - * @param e Mouse move event. - */ - protected onMouseMove_(e: PointerEvent) { - // Calculate angle. - const bBox = this.gauge!.ownerSVGElement!.getBoundingClientRect(); - const dx = e.clientX - bBox.left - FieldAngle.HALF; - const dy = e.clientY - bBox.top - FieldAngle.HALF; - let angle = Math.atan(-dy / dx); - if (isNaN(angle)) { - // This shouldn't happen, but let's not let this error propagate further. - return; - } - angle = math.toDegrees(angle); - // 0: East, 90: North, 180: West, 270: South. - if (dx < 0) { - angle += 180; - } else if (dy > 0) { - angle += 360; - } - - // Do offsetting. - if (this.clockwise) { - angle = this.offset + 360 - angle; - } else { - angle = 360 - (this.offset - angle); - } - - this.displayMouseOrKeyboardValue(angle); - } - - /** - * Handles and displays values that are input via mouse or arrow key input. - * These values need to be rounded and wrapped before being displayed so - * that the text input's value is appropriate. - * - * @param angle New angle. - */ - private displayMouseOrKeyboardValue(angle: number) { - if (this.round) { - angle = Math.round(angle / this.round) * this.round; - } - angle = this.wrapValue(angle); - if (angle !== this.value_) { - // Intermediate value changes from user input are not confirmed until the - // user closes the editor, and may be numerous. Inhibit reporting these as - // normal block change events, and instead report them as special - // intermediate changes that do not get recorded in undo history. - const oldValue = this.value_; - this.setEditorValue_(angle, false); - if ( - this.sourceBlock_ && - eventUtils.isEnabled() && - this.value_ !== oldValue - ) { - eventUtils.fire( - new (eventUtils.get(eventUtils.BLOCK_FIELD_INTERMEDIATE_CHANGE))( - this.sourceBlock_, - this.name || null, - oldValue, - this.value_, - ), - ); - } - } - } - - /** Redraw the graph with the current angle. */ - private updateGraph() { - if (!this.gauge || !this.line) { - return; - } - // Always display the input (i.e. getText) even if it is invalid. - let angleDegrees = Number(this.getText()) + this.offset; - angleDegrees %= 360; - let angleRadians = math.toRadians(angleDegrees); - const path = ['M ', FieldAngle.HALF, ',', FieldAngle.HALF]; - let x2 = FieldAngle.HALF; - let y2 = FieldAngle.HALF; - if (!isNaN(angleRadians)) { - const clockwiseFlag = Number(this.clockwise); - const angle1 = math.toRadians(this.offset); - const x1 = Math.cos(angle1) * FieldAngle.RADIUS; - const y1 = Math.sin(angle1) * -FieldAngle.RADIUS; - if (clockwiseFlag) { - angleRadians = 2 * angle1 - angleRadians; - } - x2 += Math.cos(angleRadians) * FieldAngle.RADIUS; - y2 -= Math.sin(angleRadians) * FieldAngle.RADIUS; - // Don't ask how the flag calculations work. They just do. - let largeFlag = Math.abs( - Math.floor((angleRadians - angle1) / Math.PI) % 2, - ); - if (clockwiseFlag) { - largeFlag = 1 - largeFlag; - } - path.push( - ' l ', - x1, - ',', - y1, - ' A ', - FieldAngle.RADIUS, - ',', - FieldAngle.RADIUS, - ' 0 ', - largeFlag, - ' ', - clockwiseFlag, - ' ', - x2, - ',', - y2, - ' z', - ); - } - this.gauge.setAttribute('d', path.join('')); - this.line.setAttribute('x2', `${x2}`); - this.line.setAttribute('y2', `${y2}`); - } - - /** - * Handle key down to the editor. - * - * @param e Keyboard event. - */ - protected override onHtmlInputKeyDown_(e: KeyboardEvent) { - super.onHtmlInputKeyDown_(e); - const block = this.getSourceBlock(); - if (!block) { - throw new UnattachedFieldError(); - } - - let multiplier = 0; - switch (e.key) { - case 'ArrowLeft': - // decrement (increment in RTL) - multiplier = block.RTL ? 1 : -1; - break; - case 'ArrowRight': - // increment (decrement in RTL) - multiplier = block.RTL ? -1 : 1; - break; - case 'ArrowDown': - // decrement - multiplier = -1; - break; - case 'ArrowUp': - // increment - multiplier = 1; - break; - } - if (multiplier) { - const value = this.getValue() as number; - this.displayMouseOrKeyboardValue(value + multiplier * this.round); - e.preventDefault(); - e.stopPropagation(); - } - } - - /** - * Ensure that the input value is a valid angle. - * - * @param newValue The input value. - * @returns A valid angle, or null if invalid. - */ - protected override doClassValidation_(newValue?: any): number | null { - const value = Number(newValue); - if (isNaN(value) || !isFinite(value)) { - return null; - } - return this.wrapValue(value); - } - - /** - * Wraps the value so that it is in the range (-360 + wrap, wrap). - * - * @param value The value to wrap. - * @returns The wrapped value. - */ - private wrapValue(value: number): number { - value %= 360; - if (value < 0) { - value += 360; - } - if (value > this.wrap) { - value -= 360; - } - return value; - } - - /** - * Construct a FieldAngle from a JSON arg object. - * - * @param options A JSON object with options (angle). - * @returns The new field instance. - * @nocollapse - * @internal - */ - static fromJson(options: FieldAngleFromJsonConfig): FieldAngle { - // `this` might be a subclass of FieldAngle if that class doesn't override - // the static fromJson method. - return new this(options.angle, undefined, options); - } -} - -fieldRegistry.register('field_angle', FieldAngle); - -FieldAngle.prototype.DEFAULT_VALUE = 0; - -/** - * CSS for angle field. - */ -Css.register(` -.blocklyAngleCircle { - stroke: #444; - stroke-width: 1; - fill: #ddd; - fill-opacity: 0.8; -} - -.blocklyAngleMarks { - stroke: #444; - stroke-width: 1; -} - -.blocklyAngleGauge { - fill: #f88; - fill-opacity: 0.8; - pointer-events: none; -} - -.blocklyAngleLine { - stroke: #f00; - stroke-width: 2; - stroke-linecap: round; - pointer-events: none; -} -`); - -/** - * The two main modes of the angle field. - * Compass specifies: - * - clockwise: true - * - offset: 90 - * - wrap: 0 - * - round: 15 - * - * Protractor specifies: - * - clockwise: false - * - offset: 0 - * - wrap: 0 - * - round: 15 - */ -export enum Mode { - COMPASS = 'compass', - PROTRACTOR = 'protractor', -} - -/** - * Extra configuration options for the angle field. - */ -export interface FieldAngleConfig extends FieldInputConfig { - mode?: Mode; - clockwise?: boolean; - offset?: number; - wrap?: number; - round?: number; -} - -/** - * fromJson configuration options for the angle field. - */ -export interface FieldAngleFromJsonConfig extends FieldAngleConfig { - angle?: number; -} - -/** - * A function that is called to validate changes to the field's value before - * they are set. - * - * @see {@link https://developers.google.com/blockly/guides/create-custom-blocks/fields/validators#return_values} - * @param newValue The value to be validated. - * @returns One of three instructions for setting the new value: `T`, `null`, - * or `undefined`. - * - * - `T` to set this function's returned value instead of `newValue`. - * - * - `null` to invoke `doValueInvalid_` and not set a value. - * - * - `undefined` to set `newValue` as is. - */ -export type FieldAngleValidator = FieldInputValidator; diff --git a/core/field_colour.ts b/core/field_colour.ts deleted file mode 100644 index 6b78a2e50..000000000 --- a/core/field_colour.ts +++ /dev/null @@ -1,721 +0,0 @@ -/** - * @license - * Copyright 2012 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * Colour input field. - * - * @class - */ -// Former goog.module ID: Blockly.FieldColour - -// Unused import preserved for side-effects. Remove if unneeded. -import './events/events_block_change.js'; - -import {BlockSvg} from './block_svg.js'; -import * as browserEvents from './browser_events.js'; -import * as Css from './css.js'; -import * as dom from './utils/dom.js'; -import * as dropDownDiv from './dropdowndiv.js'; -import { - Field, - FieldConfig, - FieldValidator, - UnattachedFieldError, -} from './field.js'; -import * as fieldRegistry from './field_registry.js'; -import * as aria from './utils/aria.js'; -import * as colour from './utils/colour.js'; -import * as idGenerator from './utils/idgenerator.js'; -import {Size} from './utils/size.js'; - -/** - * Class for a colour input field. - */ -export class FieldColour extends Field { - /** - * An array of colour strings for the palette. - * Copied from goog.ui.ColorPicker.SIMPLE_GRID_COLORS - * All colour pickers use this unless overridden with setColours. - */ - // prettier-ignore - static COLOURS: string[] = [ - // grays - '#ffffff', '#cccccc', '#c0c0c0', '#999999', - '#666666', '#333333', '#000000', // reds - '#ffcccc', '#ff6666', '#ff0000', '#cc0000', - '#990000', '#660000', '#330000', // oranges - '#ffcc99', '#ff9966', '#ff9900', '#ff6600', - '#cc6600', '#993300', '#663300', // yellows - '#ffff99', '#ffff66', '#ffcc66', '#ffcc33', - '#cc9933', '#996633', '#663333', // olives - '#ffffcc', '#ffff33', '#ffff00', '#ffcc00', - '#999900', '#666600', '#333300', // greens - '#99ff99', '#66ff99', '#33ff33', '#33cc00', - '#009900', '#006600', '#003300', // turquoises - '#99ffff', '#33ffff', '#66cccc', '#00cccc', - '#339999', '#336666', '#003333', // blues - '#ccffff', '#66ffff', '#33ccff', '#3366ff', - '#3333ff', '#000099', '#000066', // purples - '#ccccff', '#9999ff', '#6666cc', '#6633ff', - '#6600cc', '#333399', '#330099', // violets - '#ffccff', '#ff99ff', '#cc66cc', '#cc33cc', - '#993399', '#663366', '#330033', - ]; - - /** - * An array of tooltip strings for the palette. If not the same length as - * COLOURS, the colour's hex code will be used for any missing titles. - * All colour pickers use this unless overridden with setColours. - */ - static TITLES: string[] = []; - - /** - * Number of columns in the palette. - * All colour pickers use this unless overridden with setColumns. - */ - static COLUMNS = 7; - - /** The field's colour picker element. */ - private picker: HTMLElement | null = null; - - /** Index of the currently highlighted element. */ - private highlightedIndex: number | null = null; - - /** - * Array holding info needed to unbind events. - * Used for disposing. - * Ex: [[node, name, func], [node, name, func]]. - */ - private boundEvents: browserEvents.Data[] = []; - - /** - * Serializable fields are saved by the serializer, non-serializable fields - * are not. Editable fields should also be serializable. - */ - override SERIALIZABLE = true; - - /** Mouse cursor style when over the hotspot that initiates the editor. */ - override CURSOR = 'default'; - - /** - * Used to tell if the field needs to be rendered the next time the block is - * rendered. Colour fields are statically sized, and only need to be - * rendered at initialization. - */ - protected override isDirty_ = false; - - /** Array of colours used by this field. If null, use the global list. */ - private colours: string[] | null = null; - - /** - * Array of colour tooltips used by this field. If null, use the global - * list. - */ - private titles: string[] | null = null; - - /** - * Number of colour columns used by this field. If 0, use the global - * setting. By default use the global constants for columns. - */ - private columns = 0; - - /** - * @param value The initial value of the field. Should be in '#rrggbb' - * format. Defaults to the first value in the default colour array. Also - * accepts Field.SKIP_SETUP if you wish to skip setup (only used by - * subclasses that want to handle configuration and setting the field - * value after their own constructors have run). - * @param validator A function that is called to validate changes to the - * field's value. Takes in a colour string & returns a validated colour - * string ('#rrggbb' format), or null to abort the change. - * @param config A map of options used to configure the field. - * See the [field creation documentation]{@link - * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/colour} - * for a list of properties this parameter supports. - */ - constructor( - value?: string | typeof Field.SKIP_SETUP, - validator?: FieldColourValidator, - config?: FieldColourConfig, - ) { - super(Field.SKIP_SETUP); - - if (value === Field.SKIP_SETUP) return; - if (config) { - this.configure_(config); - } - this.setValue(value); - if (validator) { - this.setValidator(validator); - } - } - - /** - * Configure the field based on the given map of options. - * - * @param config A map of options to configure the field based on. - */ - protected override configure_(config: FieldColourConfig) { - super.configure_(config); - if (config.colourOptions) this.colours = config.colourOptions; - if (config.colourTitles) this.titles = config.colourTitles; - if (config.columns) this.columns = config.columns; - } - - /** - * Create the block UI for this colour field. - */ - override initView() { - this.size_ = new Size( - this.getConstants()!.FIELD_COLOUR_DEFAULT_WIDTH, - this.getConstants()!.FIELD_COLOUR_DEFAULT_HEIGHT, - ); - this.createBorderRect_(); - this.getBorderRect().style['fillOpacity'] = '1'; - this.getBorderRect().setAttribute('stroke', '#fff'); - if (this.isFullBlockField()) { - this.clickTarget_ = (this.sourceBlock_ as BlockSvg).getSvgRoot(); - } - } - - protected override isFullBlockField(): boolean { - const block = this.getSourceBlock(); - if (!block) throw new UnattachedFieldError(); - - const constants = this.getConstants(); - return block.isSimpleReporter() && !!constants?.FIELD_COLOUR_FULL_BLOCK; - } - - /** - * Updates text field to match the colour/style of the block. - */ - override applyColour() { - const block = this.getSourceBlock() as BlockSvg | null; - if (!block) throw new UnattachedFieldError(); - - if (!this.fieldGroup_) return; - - const borderRect = this.borderRect_; - if (!borderRect) { - throw new Error('The border rect has not been initialized'); - } - - if (!this.isFullBlockField()) { - borderRect.style.display = 'block'; - borderRect.style.fill = this.getValue() as string; - } else { - borderRect.style.display = 'none'; - // In general, do *not* let fields control the color of blocks. Having the - // field control the color is unexpected, and could have performance - // impacts. - block.pathObject.svgPath.setAttribute('fill', this.getValue() as string); - block.pathObject.svgPath.setAttribute('stroke', '#fff'); - } - } - - /** - * Returns the height and width of the field. - * - * This should *in general* be the only place render_ gets called from. - * - * @returns Height and width. - */ - override getSize(): Size { - if (this.getConstants()?.FIELD_COLOUR_FULL_BLOCK) { - // In general, do *not* let fields control the color of blocks. Having the - // field control the color is unexpected, and could have performance - // impacts. - // Full block fields have more control of the block than they should - // (i.e. updating fill colour). Whenever we get the size, the field may - // no longer be a full-block field, so we need to rerender. - this.render_(); - this.isDirty_ = false; - } - return super.getSize(); - } - - /** - * Updates the colour of the block to reflect whether this is a full - * block field or not. - */ - protected override render_() { - super.render_(); - - const block = this.getSourceBlock() as BlockSvg | null; - if (!block) throw new UnattachedFieldError(); - // Calling applyColour updates the UI (full-block vs non-full-block) for the - // colour field, and the colour of the field/block. - block.applyColour(); - } - - /** - * Updates the size of the field based on whether it is a full block field - * or not. - * - * @param margin margin to use when positioning the field. - */ - protected updateSize_(margin?: number) { - const constants = this.getConstants(); - let totalWidth; - let totalHeight; - if (this.isFullBlockField()) { - const xOffset = margin ?? 0; - totalWidth = xOffset * 2; - totalHeight = constants!.FIELD_TEXT_HEIGHT; - } else { - totalWidth = constants!.FIELD_COLOUR_DEFAULT_WIDTH; - totalHeight = constants!.FIELD_COLOUR_DEFAULT_HEIGHT; - } - - this.size_.height = totalHeight; - this.size_.width = totalWidth; - - this.positionBorderRect_(); - } - - /** - * Ensure that the input value is a valid colour. - * - * @param newValue The input value. - * @returns A valid colour, or null if invalid. - */ - protected override doClassValidation_(newValue?: any): string | null { - if (typeof newValue !== 'string') { - return null; - } - return colour.parse(newValue); - } - - /** - * Get the text for this field. Used when the block is collapsed. - * - * @returns Text representing the value of this field. - */ - override getText(): string { - let colour = this.value_ as string; - // Try to use #rgb format if possible, rather than #rrggbb. - if (/^#(.)\1(.)\2(.)\3$/.test(colour)) { - colour = '#' + colour[1] + colour[3] + colour[5]; - } - return colour; - } - - /** - * Set a custom colour grid for this field. - * - * @param colours Array of colours for this block, or null to use default - * (FieldColour.COLOURS). - * @param titles Optional array of colour tooltips, or null to use default - * (FieldColour.TITLES). - * @returns Returns itself (for method chaining). - */ - setColours(colours: string[], titles?: string[]): FieldColour { - this.colours = colours; - if (titles) { - this.titles = titles; - } - return this; - } - - /** - * Set a custom grid size for this field. - * - * @param columns Number of columns for this block, or 0 to use default - * (FieldColour.COLUMNS). - * @returns Returns itself (for method chaining). - */ - setColumns(columns: number): FieldColour { - this.columns = columns; - return this; - } - - /** Create and show the colour field's editor. */ - protected override showEditor_() { - this.dropdownCreate(); - dropDownDiv.getContentDiv().appendChild(this.picker!); - - dropDownDiv.showPositionedByField(this, this.dropdownDispose.bind(this)); - - // Focus so we can start receiving keyboard events. - this.picker!.focus({preventScroll: true}); - } - - /** - * Handle a click on a colour cell. - * - * @param e Mouse event. - */ - private onClick(e: PointerEvent) { - const cell = e.target as Element; - const colour = cell && cell.getAttribute('data-colour'); - if (colour !== null) { - this.setValue(colour); - dropDownDiv.hideIfOwner(this); - } - } - - /** - * Handle a key down event. Navigate around the grid with the - * arrow keys. Enter selects the highlighted colour. - * - * @param e Keyboard event. - */ - private onKeyDown(e: KeyboardEvent) { - let handled = true; - let highlighted: HTMLElement | null; - switch (e.key) { - case 'ArrowUp': - this.moveHighlightBy(0, -1); - break; - case 'ArrowDown': - this.moveHighlightBy(0, 1); - break; - case 'ArrowLeft': - this.moveHighlightBy(-1, 0); - break; - case 'ArrowRight': - this.moveHighlightBy(1, 0); - break; - case 'Enter': - // Select the highlighted colour. - highlighted = this.getHighlighted(); - if (highlighted) { - const colour = highlighted.getAttribute('data-colour'); - if (colour !== null) { - this.setValue(colour); - } - } - dropDownDiv.hideWithoutAnimation(); - break; - default: - handled = false; - } - if (handled) { - e.stopPropagation(); - } - } - - /** - * Move the currently highlighted position by dx and dy. - * - * @param dx Change of x. - * @param dy Change of y. - */ - private moveHighlightBy(dx: number, dy: number) { - if (!this.highlightedIndex) { - return; - } - - const colours = this.colours || FieldColour.COLOURS; - const columns = this.columns || FieldColour.COLUMNS; - - // Get the current x and y coordinates. - let x = this.highlightedIndex % columns; - let y = Math.floor(this.highlightedIndex / columns); - - // Add the offset. - x += dx; - y += dy; - - if (dx < 0) { - // Move left one grid cell, even in RTL. - // Loop back to the end of the previous row if we have room. - if (x < 0 && y > 0) { - x = columns - 1; - y--; - } else if (x < 0) { - x = 0; - } - } else if (dx > 0) { - // Move right one grid cell, even in RTL. - // Loop to the start of the next row, if there's room. - if (x > columns - 1 && y < Math.floor(colours.length / columns) - 1) { - x = 0; - y++; - } else if (x > columns - 1) { - x--; - } - } else if (dy < 0) { - // Move up one grid cell, stop at the top. - if (y < 0) { - y = 0; - } - } else if (dy > 0) { - // Move down one grid cell, stop at the bottom. - if (y > Math.floor(colours.length / columns) - 1) { - y = Math.floor(colours.length / columns) - 1; - } - } - - // Move the highlight to the new coordinates. - const cell = this.picker!.childNodes[y].childNodes[x] as Element; - const index = y * columns + x; - this.setHighlightedCell(cell, index); - } - - /** - * Handle a mouse move event. Highlight the hovered colour. - * - * @param e Mouse event. - */ - private onMouseMove(e: PointerEvent) { - const cell = e.target as Element; - const index = cell && Number(cell.getAttribute('data-index')); - if (index !== null && index !== this.highlightedIndex) { - this.setHighlightedCell(cell, index); - } - } - - /** Handle a mouse enter event. Focus the picker. */ - private onMouseEnter() { - this.picker?.focus({preventScroll: true}); - } - - /** - * Handle a mouse leave event. Blur the picker and unhighlight - * the currently highlighted colour. - */ - private onMouseLeave() { - this.picker?.blur(); - const highlighted = this.getHighlighted(); - if (highlighted) { - dom.removeClass(highlighted, 'blocklyColourHighlighted'); - } - } - - /** - * Returns the currently highlighted item (if any). - * - * @returns Highlighted item (null if none). - */ - private getHighlighted(): HTMLElement | null { - if (!this.highlightedIndex) { - return null; - } - - const columns = this.columns || FieldColour.COLUMNS; - const x = this.highlightedIndex % columns; - const y = Math.floor(this.highlightedIndex / columns); - const row = this.picker!.childNodes[y]; - if (!row) { - return null; - } - return row.childNodes[x] as HTMLElement; - } - - /** - * Update the currently highlighted cell. - * - * @param cell The new cell to highlight. - * @param index The index of the new cell. - */ - private setHighlightedCell(cell: Element, index: number) { - // Unhighlight the current item. - const highlighted = this.getHighlighted(); - if (highlighted) { - dom.removeClass(highlighted, 'blocklyColourHighlighted'); - } - // Highlight new item. - dom.addClass(cell, 'blocklyColourHighlighted'); - // Set new highlighted index. - this.highlightedIndex = index; - - // Update accessibility roles. - const cellId = cell.getAttribute('id'); - if (cellId && this.picker) { - aria.setState(this.picker, aria.State.ACTIVEDESCENDANT, cellId); - } - } - - /** Create a colour picker dropdown editor. */ - private dropdownCreate() { - const columns = this.columns || FieldColour.COLUMNS; - const colours = this.colours || FieldColour.COLOURS; - const titles = this.titles || FieldColour.TITLES; - const selectedColour = this.getValue(); - // Create the palette. - const table = document.createElement('table'); - table.className = 'blocklyColourTable'; - table.tabIndex = 0; - table.dir = 'ltr'; - aria.setRole(table, aria.Role.GRID); - aria.setState(table, aria.State.EXPANDED, true); - aria.setState( - table, - aria.State.ROWCOUNT, - Math.floor(colours.length / columns), - ); - aria.setState(table, aria.State.COLCOUNT, columns); - let row: Element; - for (let i = 0; i < colours.length; i++) { - if (i % columns === 0) { - row = document.createElement('tr'); - aria.setRole(row, aria.Role.ROW); - table.appendChild(row); - } - const cell = document.createElement('td'); - row!.appendChild(cell); - // This becomes the value, if clicked. - cell.setAttribute('data-colour', colours[i]); - cell.title = titles[i] || colours[i]; - cell.id = idGenerator.getNextUniqueId(); - cell.setAttribute('data-index', `${i}`); - aria.setRole(cell, aria.Role.GRIDCELL); - aria.setState(cell, aria.State.LABEL, colours[i]); - aria.setState(cell, aria.State.SELECTED, colours[i] === selectedColour); - cell.style.backgroundColor = colours[i]; - if (colours[i] === selectedColour) { - cell.className = 'blocklyColourSelected'; - this.highlightedIndex = i; - } - } - - // Configure event handler on the table to listen for any event in a cell. - this.boundEvents.push( - browserEvents.conditionalBind( - table, - 'pointerdown', - this, - this.onClick, - true, - ), - ); - this.boundEvents.push( - browserEvents.conditionalBind( - table, - 'pointermove', - this, - this.onMouseMove, - true, - ), - ); - this.boundEvents.push( - browserEvents.conditionalBind( - table, - 'pointerenter', - this, - this.onMouseEnter, - true, - ), - ); - this.boundEvents.push( - browserEvents.conditionalBind( - table, - 'pointerleave', - this, - this.onMouseLeave, - true, - ), - ); - this.boundEvents.push( - browserEvents.conditionalBind( - table, - 'keydown', - this, - this.onKeyDown, - false, - ), - ); - - this.picker = table; - } - - /** Disposes of events and DOM-references belonging to the colour editor. */ - private dropdownDispose() { - for (const event of this.boundEvents) { - browserEvents.unbind(event); - } - this.boundEvents.length = 0; - this.picker = null; - this.highlightedIndex = null; - } - - /** - * Construct a FieldColour from a JSON arg object. - * - * @param options A JSON object with options (colour). - * @returns The new field instance. - * @nocollapse - * @internal - */ - static fromJson(options: FieldColourFromJsonConfig): FieldColour { - // `this` might be a subclass of FieldColour if that class doesn't override - // the static fromJson method. - return new this(options.colour, undefined, options); - } -} - -/** The default value for this field. */ -FieldColour.prototype.DEFAULT_VALUE = FieldColour.COLOURS[0]; - -fieldRegistry.register('field_colour', FieldColour); - -/** - * CSS for colour picker. - */ -Css.register(` -.blocklyColourTable { - border-collapse: collapse; - display: block; - outline: none; - padding: 1px; -} - -.blocklyColourTable>tr>td { - border: 0.5px solid #888; - box-sizing: border-box; - cursor: pointer; - display: inline-block; - height: 20px; - padding: 0; - width: 20px; -} - -.blocklyColourTable>tr>td.blocklyColourHighlighted { - border-color: #eee; - box-shadow: 2px 2px 7px 2px rgba(0, 0, 0, 0.3); - position: relative; -} - -.blocklyColourSelected, .blocklyColourSelected:hover { - border-color: #eee !important; - outline: 1px solid #333; - position: relative; -} -`); - -/** - * Config options for the colour field. - */ -export interface FieldColourConfig extends FieldConfig { - colourOptions?: string[]; - colourTitles?: string[]; - columns?: number; -} - -/** - * fromJson config options for the colour field. - */ -export interface FieldColourFromJsonConfig extends FieldColourConfig { - colour?: string; -} - -/** - * A function that is called to validate changes to the field's value before - * they are set. - * - * @see {@link https://developers.google.com/blockly/guides/create-custom-blocks/fields/validators#return_values} - * @param newValue The value to be validated. - * @returns One of three instructions for setting the new value: `T`, `null`, - * or `undefined`. - * - * - `T` to set this function's returned value instead of `newValue`. - * - * - `null` to invoke `doValueInvalid_` and not set a value. - * - * - `undefined` to set `newValue` as is. - */ -export type FieldColourValidator = FieldValidator; diff --git a/core/field_image.ts b/core/field_image.ts index 6d31a9772..cc3838cf5 100644 --- a/core/field_image.ts +++ b/core/field_image.ts @@ -284,7 +284,7 @@ export interface FieldImageConfig extends FieldConfig { } /** - * fromJson config options for the colour field. + * fromJson config options for the image field. */ export interface FieldImageFromJsonConfig extends FieldImageConfig { src?: string; diff --git a/core/field_multilineinput.ts b/core/field_multilineinput.ts deleted file mode 100644 index b1ce0cb10..000000000 --- a/core/field_multilineinput.ts +++ /dev/null @@ -1,526 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * Text Area field. - * - * @class - */ -// Former goog.module ID: Blockly.FieldMultilineInput - -import * as Css from './css.js'; -import {Field, UnattachedFieldError} from './field.js'; -import * as fieldRegistry from './field_registry.js'; -import { - FieldTextInput, - FieldTextInputConfig, - FieldTextInputValidator, -} from './field_textinput.js'; -import * as aria from './utils/aria.js'; -import * as dom from './utils/dom.js'; -import * as parsing from './utils/parsing.js'; -import {Svg} from './utils/svg.js'; -import * as userAgent from './utils/useragent.js'; -import * as WidgetDiv from './widgetdiv.js'; - -/** - * Class for an editable text area field. - */ -export class FieldMultilineInput extends FieldTextInput { - /** - * The SVG group element that will contain a text element for each text row - * when initialized. - */ - textGroup: SVGGElement | null = null; - - /** - * Defines the maximum number of lines of field. - * If exceeded, scrolling functionality is enabled. - */ - protected maxLines_ = Infinity; - - /** Whether Y overflow is currently occurring. */ - protected isOverflowedY_ = false; - - /** - * @param value The initial content of the field. Should cast to a string. - * Defaults to an empty string if null or undefined. Also accepts - * Field.SKIP_SETUP if you wish to skip setup (only used by subclasses - * that want to handle configuration and setting the field value after - * their own constructors have run). - * @param validator An optional function that is called to validate any - * constraints on what the user entered. Takes the new text as an - * argument and returns either the accepted text, a replacement text, or - * null to abort the change. - * @param config A map of options used to configure the field. - * See the [field creation documentation]{@link - * https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/multiline-text-input#creation} - * for a list of properties this parameter supports. - */ - constructor( - value?: string | typeof Field.SKIP_SETUP, - validator?: FieldMultilineInputValidator, - config?: FieldMultilineInputConfig, - ) { - super(Field.SKIP_SETUP); - - if (value === Field.SKIP_SETUP) return; - if (config) { - this.configure_(config); - } - this.setValue(value); - if (validator) { - this.setValidator(validator); - } - } - - /** - * Configure the field based on the given map of options. - * - * @param config A map of options to configure the field based on. - */ - protected override configure_(config: FieldMultilineInputConfig) { - super.configure_(config); - if (config.maxLines) this.setMaxLines(config.maxLines); - } - - /** - * Serializes this field's value to XML. Should only be called by Blockly.Xml. - * - * @param fieldElement The element to populate with info about the field's - * state. - * @returns The element containing info about the field's state. - * @internal - */ - override toXml(fieldElement: Element): Element { - // Replace '\n' characters with HTML-escaped equivalent ' '. This is - // needed so the plain-text representation of the XML produced by - // `Blockly.Xml.domToText` will appear on a single line (this is a - // limitation of the plain-text format). - fieldElement.textContent = (this.getValue() as string).replace( - /\n/g, - ' ', - ); - return fieldElement; - } - - /** - * Sets the field's value based on the given XML element. Should only be - * called by Blockly.Xml. - * - * @param fieldElement The element containing info about the field's state. - * @internal - */ - override fromXml(fieldElement: Element) { - this.setValue(fieldElement.textContent!.replace(/ /g, '\n')); - } - - /** - * Saves this field's value. - * This function only exists for subclasses of FieldMultilineInput which - * predate the load/saveState API and only define to/fromXml. - * - * @returns The state of this field. - * @internal - */ - override saveState(): AnyDuringMigration { - const legacyState = this.saveLegacyState(FieldMultilineInput); - if (legacyState !== null) { - return legacyState; - } - return this.getValue(); - } - - /** - * Sets the field's value based on the given state. - * This function only exists for subclasses of FieldMultilineInput which - * predate the load/saveState API and only define to/fromXml. - * - * @param state The state of the variable to assign to this variable field. - * @internal - */ - override loadState(state: AnyDuringMigration) { - if (this.loadLegacyState(Field, state)) { - return; - } - this.setValue(state); - } - - /** - * Create the block UI for this field. - */ - override initView() { - this.createBorderRect_(); - this.textGroup = dom.createSvgElement( - Svg.G, - { - 'class': 'blocklyEditableText', - }, - this.fieldGroup_, - ); - } - - /** - * Get the text from this field as displayed on screen. May differ from - * getText due to ellipsis, and other formatting. - * - * @returns Currently displayed text. - */ - protected override getDisplayText_(): string { - const block = this.getSourceBlock(); - if (!block) { - throw new UnattachedFieldError(); - } - let textLines = this.getText(); - if (!textLines) { - // Prevent the field from disappearing if empty. - return Field.NBSP; - } - const lines = textLines.split('\n'); - textLines = ''; - const displayLinesNumber = this.isOverflowedY_ - ? this.maxLines_ - : lines.length; - for (let i = 0; i < displayLinesNumber; i++) { - let text = lines[i]; - if (text.length > this.maxDisplayLength) { - // Truncate displayed string and add an ellipsis ('...'). - text = text.substring(0, this.maxDisplayLength - 4) + '...'; - } else if (this.isOverflowedY_ && i === displayLinesNumber - 1) { - text = text.substring(0, text.length - 3) + '...'; - } - // Replace whitespace with non-breaking spaces so the text doesn't - // collapse. - text = text.replace(/\s/g, Field.NBSP); - - textLines += text; - if (i !== displayLinesNumber - 1) { - textLines += '\n'; - } - } - if (block.RTL) { - // The SVG is LTR, force value to be RTL. - textLines += '\u200F'; - } - return textLines; - } - - /** - * Called by setValue if the text input is valid. Updates the value of the - * field, and updates the text of the field if it is not currently being - * edited (i.e. handled by the htmlInput_). Is being redefined here to update - * overflow state of the field. - * - * @param newValue The value to be saved. The default validator guarantees - * that this is a string. - */ - protected override doValueUpdate_(newValue: string) { - super.doValueUpdate_(newValue); - if (this.value_ !== null) { - this.isOverflowedY_ = this.value_.split('\n').length > this.maxLines_; - } - } - - /** Updates the text of the textElement. */ - protected override render_() { - const block = this.getSourceBlock(); - if (!block) { - throw new UnattachedFieldError(); - } - // Remove all text group children. - let currentChild; - const textGroup = this.textGroup; - while ((currentChild = textGroup!.firstChild)) { - textGroup!.removeChild(currentChild); - } - - // Add in text elements into the group. - const lines = this.getDisplayText_().split('\n'); - let y = 0; - for (let i = 0; i < lines.length; i++) { - const lineHeight = - this.getConstants()!.FIELD_TEXT_HEIGHT + - this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING; - const span = dom.createSvgElement( - Svg.TEXT, - { - 'class': 'blocklyText blocklyMultilineText', - 'x': this.getConstants()!.FIELD_BORDER_RECT_X_PADDING, - 'y': y + this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING, - 'dy': this.getConstants()!.FIELD_TEXT_BASELINE, - }, - textGroup, - ); - span.appendChild(document.createTextNode(lines[i])); - y += lineHeight; - } - - if (this.isBeingEdited_) { - const htmlInput = this.htmlInput_ as HTMLElement; - if (this.isOverflowedY_) { - dom.addClass(htmlInput, 'blocklyHtmlTextAreaInputOverflowedY'); - } else { - dom.removeClass(htmlInput, 'blocklyHtmlTextAreaInputOverflowedY'); - } - } - - this.updateSize_(); - - if (this.isBeingEdited_) { - if (block.RTL) { - // in RTL, we need to let the browser reflow before resizing - // in order to get the correct bounding box of the borderRect - // avoiding issue #2777. - setTimeout(this.resizeEditor_.bind(this), 0); - } else { - this.resizeEditor_(); - } - const htmlInput = this.htmlInput_ as HTMLElement; - if (!this.isTextValid_) { - dom.addClass(htmlInput, 'blocklyInvalidInput'); - aria.setState(htmlInput, aria.State.INVALID, true); - } else { - dom.removeClass(htmlInput, 'blocklyInvalidInput'); - aria.setState(htmlInput, aria.State.INVALID, false); - } - } - } - - /** Updates the size of the field based on the text. */ - protected override updateSize_() { - const nodes = (this.textGroup as SVGElement).childNodes; - const fontSize = this.getConstants()!.FIELD_TEXT_FONTSIZE; - const fontWeight = this.getConstants()!.FIELD_TEXT_FONTWEIGHT; - const fontFamily = this.getConstants()!.FIELD_TEXT_FONTFAMILY; - let totalWidth = 0; - let totalHeight = 0; - for (let i = 0; i < nodes.length; i++) { - const tspan = nodes[i] as SVGTextElement; - const textWidth = dom.getFastTextWidth( - tspan, - fontSize, - fontWeight, - fontFamily, - ); - if (textWidth > totalWidth) { - totalWidth = textWidth; - } - totalHeight += - this.getConstants()!.FIELD_TEXT_HEIGHT + - (i > 0 ? this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING : 0); - } - if (this.isBeingEdited_) { - // The default width is based on the longest line in the display text, - // but when it's being edited, width should be calculated based on the - // absolute longest line, even if it would be truncated after editing. - // Otherwise we would get wrong editor width when there are more - // lines than this.maxLines_. - const actualEditorLines = String(this.value_).split('\n'); - const dummyTextElement = dom.createSvgElement(Svg.TEXT, { - 'class': 'blocklyText blocklyMultilineText', - }); - - for (let i = 0; i < actualEditorLines.length; i++) { - if (actualEditorLines[i].length > this.maxDisplayLength) { - actualEditorLines[i] = actualEditorLines[i].substring( - 0, - this.maxDisplayLength, - ); - } - dummyTextElement.textContent = actualEditorLines[i]; - const lineWidth = dom.getFastTextWidth( - dummyTextElement, - fontSize, - fontWeight, - fontFamily, - ); - if (lineWidth > totalWidth) { - totalWidth = lineWidth; - } - } - - const scrollbarWidth = - this.htmlInput_!.offsetWidth - this.htmlInput_!.clientWidth; - totalWidth += scrollbarWidth; - } - if (this.borderRect_) { - totalHeight += this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING * 2; - totalWidth += this.getConstants()!.FIELD_BORDER_RECT_X_PADDING * 2; - this.borderRect_.setAttribute('width', `${totalWidth}`); - this.borderRect_.setAttribute('height', `${totalHeight}`); - } - this.size_.width = totalWidth; - this.size_.height = totalHeight; - - this.positionBorderRect_(); - } - - /** - * Show the inline free-text editor on top of the text. - * Overrides the default behaviour to force rerender in order to - * correct block size, based on editor text. - * - * @param e Optional mouse event that triggered the field to open, or - * undefined if triggered programmatically. - * @param quietInput True if editor should be created without focus. - * Defaults to false. - */ - override showEditor_(e?: Event, quietInput?: boolean) { - super.showEditor_(e, quietInput); - this.forceRerender(); - } - - /** - * Create the text input editor widget. - * - * @returns The newly created text input editor. - */ - protected override widgetCreate_(): HTMLTextAreaElement { - const div = WidgetDiv.getDiv(); - const scale = this.workspace_!.getScale(); - - const htmlInput = document.createElement('textarea'); - htmlInput.className = 'blocklyHtmlInput blocklyHtmlTextAreaInput'; - htmlInput.setAttribute('spellcheck', String(this.spellcheck_)); - const fontSize = this.getConstants()!.FIELD_TEXT_FONTSIZE * scale + 'pt'; - div!.style.fontSize = fontSize; - htmlInput.style.fontSize = fontSize; - const borderRadius = FieldTextInput.BORDERRADIUS * scale + 'px'; - htmlInput.style.borderRadius = borderRadius; - const paddingX = this.getConstants()!.FIELD_BORDER_RECT_X_PADDING * scale; - const paddingY = - (this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING * scale) / 2; - htmlInput.style.padding = - paddingY + 'px ' + paddingX + 'px ' + paddingY + 'px ' + paddingX + 'px'; - const lineHeight = - this.getConstants()!.FIELD_TEXT_HEIGHT + - this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING; - htmlInput.style.lineHeight = lineHeight * scale + 'px'; - - div!.appendChild(htmlInput); - - htmlInput.value = htmlInput.defaultValue = this.getEditorText_(this.value_); - htmlInput.setAttribute('data-untyped-default-value', String(this.value_)); - htmlInput.setAttribute('data-old-value', ''); - if (userAgent.GECKO) { - // In FF, ensure the browser reflows before resizing to avoid issue #2777. - setTimeout(this.resizeEditor_.bind(this), 0); - } else { - this.resizeEditor_(); - } - - this.bindInputEvents_(htmlInput); - - return htmlInput; - } - - /** - * Sets the maxLines config for this field. - * - * @param maxLines Defines the maximum number of lines allowed, before - * scrolling functionality is enabled. - */ - setMaxLines(maxLines: number) { - if ( - typeof maxLines === 'number' && - maxLines > 0 && - maxLines !== this.maxLines_ - ) { - this.maxLines_ = maxLines; - this.forceRerender(); - } - } - - /** - * Returns the maxLines config of this field. - * - * @returns The maxLines config value. - */ - getMaxLines(): number { - return this.maxLines_; - } - - /** - * Handle key down to the editor. Override the text input definition of this - * so as to not close the editor when enter is typed in. - * - * @param e Keyboard event. - */ - protected override onHtmlInputKeyDown_(e: KeyboardEvent) { - if (e.key !== 'Enter') { - super.onHtmlInputKeyDown_(e); - } - } - - /** - * Construct a FieldMultilineInput from a JSON arg object, - * dereferencing any string table references. - * - * @param options A JSON object with options (text, and spellcheck). - * @returns The new field instance. - * @nocollapse - * @internal - */ - static override fromJson( - options: FieldMultilineInputFromJsonConfig, - ): FieldMultilineInput { - const text = parsing.replaceMessageReferences(options.text); - // `this` might be a subclass of FieldMultilineInput if that class doesn't - // override the static fromJson method. - return new this(text, undefined, options); - } -} - -fieldRegistry.register('field_multilinetext', FieldMultilineInput); - -/** - * CSS for multiline field. - */ -Css.register(` -.blocklyHtmlTextAreaInput { - font-family: monospace; - resize: none; - overflow: hidden; - height: 100%; - text-align: left; -} - -.blocklyHtmlTextAreaInputOverflowedY { - overflow-y: scroll; -} -`); - -/** - * Config options for the multiline input field. - */ -export interface FieldMultilineInputConfig extends FieldTextInputConfig { - maxLines?: number; -} - -/** - * fromJson config options for the multiline input field. - */ -export interface FieldMultilineInputFromJsonConfig - extends FieldMultilineInputConfig { - text?: string; -} - -/** - * A function that is called to validate changes to the field's value before - * they are set. - * - * @see {@link https://developers.google.com/blockly/guides/create-custom-blocks/fields/validators#return_values} - * @param newValue The value to be validated. - * @returns One of three instructions for setting the new value: `T`, `null`, - * or `undefined`. - * - * - `T` to set this function's returned value instead of `newValue`. - * - * - `null` to invoke `doValueInvalid_` and not set a value. - * - * - `undefined` to set `newValue` as is. - */ -export type FieldMultilineInputValidator = FieldTextInputValidator; diff --git a/generators/dart.ts b/generators/dart.ts index ee21b1fbd..5587e94f1 100644 --- a/generators/dart.ts +++ b/generators/dart.ts @@ -13,7 +13,6 @@ // Former goog.module ID: Blockly.Dart.all import {DartGenerator} from './dart/dart_generator.js'; -import * as colour from './dart/colour.js'; import * as lists from './dart/lists.js'; import * as logic from './dart/logic.js'; import * as loops from './dart/loops.js'; @@ -37,7 +36,6 @@ dartGenerator.addReservedWords('Html,Math'); // Install per-block-type generator functions: const generators: typeof dartGenerator.forBlock = { - ...colour, ...lists, ...logic, ...loops, diff --git a/generators/dart/colour.ts b/generators/dart/colour.ts deleted file mode 100644 index ac72fc04c..000000000 --- a/generators/dart/colour.ts +++ /dev/null @@ -1,132 +0,0 @@ -/** - * @license - * Copyright 2014 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @file Generating Dart for colour blocks. - */ - -// Former goog.module ID: Blockly.Dart.colour - -import type {Block} from '../../core/block.js'; -import type {DartGenerator} from './dart_generator.js'; -import {Order} from './dart_generator.js'; - -// RESERVED WORDS: 'Math' - -export function colour_picker( - block: Block, - generator: DartGenerator, -): [string, Order] { - // Colour picker. - const code = generator.quote_(block.getFieldValue('COLOUR')); - return [code, Order.ATOMIC]; -} - -export function colour_random( - block: Block, - generator: DartGenerator, -): [string, Order] { - // Generate a random colour. - // TODO(#7600): find better approach than casting to any to override - // CodeGenerator declaring .definitions protected. - (generator as AnyDuringMigration).definitions_['import_dart_math'] = - "import 'dart:math' as Math;"; - const functionName = generator.provideFunction_( - 'colour_random', - ` -String ${generator.FUNCTION_NAME_PLACEHOLDER_}() { - 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)]}'; -} -`, - ); - const code = functionName + '()'; - return [code, Order.UNARY_POSTFIX]; -} - -export function colour_rgb( - block: Block, - generator: DartGenerator, -): [string, Order] { - // Compose a colour from RGB components expressed as percentages. - const red = generator.valueToCode(block, 'RED', Order.NONE) || 0; - const green = generator.valueToCode(block, 'GREEN', Order.NONE) || 0; - const blue = generator.valueToCode(block, 'BLUE', Order.NONE) || 0; - - // TODO(#7600): find better approach than casting to any to override - // CodeGenerator declaring .definitions protected. - (generator as AnyDuringMigration).definitions_['import_dart_math'] = - "import 'dart:math' as Math;"; - const functionName = generator.provideFunction_( - 'colour_rgb', - ` -String ${generator.FUNCTION_NAME_PLACEHOLDER_}(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'; -} -`, - ); - const code = functionName + '(' + red + ', ' + green + ', ' + blue + ')'; - return [code, Order.UNARY_POSTFIX]; -} - -export function colour_blend( - block: Block, - generator: DartGenerator, -): [string, Order] { - // Blend two colours together. - const c1 = generator.valueToCode(block, 'COLOUR1', Order.NONE) || "'#000000'"; - const c2 = generator.valueToCode(block, 'COLOUR2', Order.NONE) || "'#000000'"; - const ratio = generator.valueToCode(block, 'RATIO', Order.NONE) || 0.5; - - // TODO(#7600): find better approach than casting to any to override - // CodeGenerator declaring .definitions protected. - (generator as AnyDuringMigration).definitions_['import_dart_math'] = - "import 'dart:math' as Math;"; - const functionName = generator.provideFunction_( - 'colour_blend', - ` -String ${generator.FUNCTION_NAME_PLACEHOLDER_}(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'; -} -`, - ); - const code = functionName + '(' + c1 + ', ' + c2 + ', ' + ratio + ')'; - return [code, Order.UNARY_POSTFIX]; -} diff --git a/generators/dart/text.ts b/generators/dart/text.ts index 108516019..1d9e8dca7 100644 --- a/generators/dart/text.ts +++ b/generators/dart/text.ts @@ -23,16 +23,6 @@ export function text(block: Block, generator: DartGenerator): [string, Order] { return [code, Order.ATOMIC]; } -export function text_multiline( - block: Block, - generator: DartGenerator, -): [string, Order] { - // Text value. - const code = generator.multiline_quote_(block.getFieldValue('TEXT')); - const order = code.indexOf('+') !== -1 ? Order.ADDITIVE : Order.ATOMIC; - return [code, order]; -} - export function text_join( block: Block, generator: DartGenerator, diff --git a/generators/javascript.ts b/generators/javascript.ts index 2358d187d..5fa6bb84d 100644 --- a/generators/javascript.ts +++ b/generators/javascript.ts @@ -13,7 +13,6 @@ // Former goog.module ID: Blockly.JavaScript.all import {JavascriptGenerator} from './javascript/javascript_generator.js'; -import * as colour from './javascript/colour.js'; import * as lists from './javascript/lists.js'; import * as logic from './javascript/logic.js'; import * as loops from './javascript/loops.js'; @@ -33,7 +32,6 @@ export const javascriptGenerator = new JavascriptGenerator(); // Install per-block-type generator functions: const generators: typeof javascriptGenerator.forBlock = { - ...colour, ...lists, ...logic, ...loops, diff --git a/generators/javascript/colour.ts b/generators/javascript/colour.ts deleted file mode 100644 index b599e76d9..000000000 --- a/generators/javascript/colour.ts +++ /dev/null @@ -1,101 +0,0 @@ -/** - * @license - * Copyright 2012 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @file Generating JavaScript for colour blocks. - */ - -// Former goog.module ID: Blockly.JavaScript.colour - -import type {Block} from '../../core/block.js'; -import type {JavascriptGenerator} from './javascript_generator.js'; -import {Order} from './javascript_generator.js'; - -export function colour_picker( - block: Block, - generator: JavascriptGenerator, -): [string, Order] { - // Colour picker. - const code = generator.quote_(block.getFieldValue('COLOUR')); - return [code, Order.ATOMIC]; -} - -export function colour_random( - block: Block, - generator: JavascriptGenerator, -): [string, Order] { - // Generate a random colour. - const functionName = generator.provideFunction_( - 'colourRandom', - ` -function ${generator.FUNCTION_NAME_PLACEHOLDER_}() { - var num = Math.floor(Math.random() * Math.pow(2, 24)); - return '#' + ('00000' + num.toString(16)).substr(-6); -} -`, - ); - const code = functionName + '()'; - return [code, Order.FUNCTION_CALL]; -} - -export function colour_rgb( - block: Block, - generator: JavascriptGenerator, -): [string, Order] { - // Compose a colour from RGB components expressed as percentages. - const red = generator.valueToCode(block, 'RED', Order.NONE) || 0; - const green = generator.valueToCode(block, 'GREEN', Order.NONE) || 0; - const blue = generator.valueToCode(block, 'BLUE', Order.NONE) || 0; - const functionName = generator.provideFunction_( - 'colourRgb', - ` -function ${generator.FUNCTION_NAME_PLACEHOLDER_}(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; -} -`, - ); - const code = functionName + '(' + red + ', ' + green + ', ' + blue + ')'; - return [code, Order.FUNCTION_CALL]; -} - -export function colour_blend( - block: Block, - generator: JavascriptGenerator, -): [string, Order] { - // Blend two colours together. - const c1 = generator.valueToCode(block, 'COLOUR1', Order.NONE) || "'#000000'"; - const c2 = generator.valueToCode(block, 'COLOUR2', Order.NONE) || "'#000000'"; - const ratio = generator.valueToCode(block, 'RATIO', Order.NONE) || 0.5; - const functionName = generator.provideFunction_( - 'colourBlend', - ` -function ${generator.FUNCTION_NAME_PLACEHOLDER_}(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; -} -`, - ); - const code = functionName + '(' + c1 + ', ' + c2 + ', ' + ratio + ')'; - return [code, Order.FUNCTION_CALL]; -} diff --git a/generators/javascript/text.ts b/generators/javascript/text.ts index 1681979ec..d31bdcf4b 100644 --- a/generators/javascript/text.ts +++ b/generators/javascript/text.ts @@ -66,16 +66,6 @@ export function text( return [code, Order.ATOMIC]; } -export function text_multiline( - block: Block, - generator: JavascriptGenerator, -): [string, Order] { - // Text value. - const code = generator.multiline_quote_(block.getFieldValue('TEXT')); - const order = code.indexOf('+') !== -1 ? Order.ADDITION : Order.ATOMIC; - return [code, order]; -} - export function text_join( block: Block, generator: JavascriptGenerator, diff --git a/generators/lua.ts b/generators/lua.ts index 5c5b0af00..0cf81fb96 100644 --- a/generators/lua.ts +++ b/generators/lua.ts @@ -12,7 +12,6 @@ // Former goog.module ID: Blockly.Lua.all import {LuaGenerator} from './lua/lua_generator.js'; -import * as colour from './lua/colour.js'; import * as lists from './lua/lists.js'; import * as logic from './lua/logic.js'; import * as loops from './lua/loops.js'; @@ -31,7 +30,6 @@ export const luaGenerator = new LuaGenerator(); // Install per-block-type generator functions: const generators: typeof luaGenerator.forBlock = { - ...colour, ...lists, ...logic, ...loops, diff --git a/generators/lua/colour.ts b/generators/lua/colour.ts deleted file mode 100644 index f4a6a8ea9..000000000 --- a/generators/lua/colour.ts +++ /dev/null @@ -1,89 +0,0 @@ -/** - * @license - * Copyright 2016 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @file Generating Lua for colour blocks. - */ - -// Former goog.module ID: Blockly.Lua.colour - -import type {Block} from '../../core/block.js'; -import type {LuaGenerator} from './lua_generator.js'; -import {Order} from './lua_generator.js'; - -export function colour_picker( - block: Block, - generator: LuaGenerator, -): [string, Order] { - // Colour picker. - const code = generator.quote_(block.getFieldValue('COLOUR')); - return [code, Order.ATOMIC]; -} - -export function colour_random( - block: Block, - generator: LuaGenerator, -): [string, Order] { - // Generate a random colour. - const code = 'string.format("#%06x", math.random(0, 2^24 - 1))'; - return [code, Order.HIGH]; -} - -export function colour_rgb( - block: Block, - generator: LuaGenerator, -): [string, Order] { - // Compose a colour from RGB components expressed as percentages. - const functionName = generator.provideFunction_( - 'colour_rgb', - ` -function ${generator.FUNCTION_NAME_PLACEHOLDER_}(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 -`, - ); - const r = generator.valueToCode(block, 'RED', Order.NONE) || 0; - const g = generator.valueToCode(block, 'GREEN', Order.NONE) || 0; - const b = generator.valueToCode(block, 'BLUE', Order.NONE) || 0; - const code = functionName + '(' + r + ', ' + g + ', ' + b + ')'; - return [code, Order.HIGH]; -} - -export function colour_blend( - block: Block, - generator: LuaGenerator, -): [string, Order] { - // Blend two colours together. - const functionName = generator.provideFunction_( - 'colour_blend', - ` -function ${generator.FUNCTION_NAME_PLACEHOLDER_}(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 -`, - ); - const colour1 = - generator.valueToCode(block, 'COLOUR1', Order.NONE) || "'#000000'"; - const colour2 = - generator.valueToCode(block, 'COLOUR2', Order.NONE) || "'#000000'"; - const ratio = generator.valueToCode(block, 'RATIO', Order.NONE) || 0; - const code = - functionName + '(' + colour1 + ', ' + colour2 + ', ' + ratio + ')'; - return [code, Order.HIGH]; -} diff --git a/generators/lua/text.ts b/generators/lua/text.ts index ff65327cf..7e3bb8794 100644 --- a/generators/lua/text.ts +++ b/generators/lua/text.ts @@ -21,16 +21,6 @@ export function text(block: Block, generator: LuaGenerator): [string, Order] { return [code, Order.ATOMIC]; } -export function text_multiline( - block: Block, - generator: LuaGenerator, -): [string, Order] { - // Text value. - const code = generator.multiline_quote_(block.getFieldValue('TEXT')); - const order = code.indexOf('..') !== -1 ? Order.CONCATENATION : Order.ATOMIC; - return [code, order]; -} - export function text_join( block: Block, generator: LuaGenerator, diff --git a/generators/php.ts b/generators/php.ts index 18631ad9a..30a080866 100644 --- a/generators/php.ts +++ b/generators/php.ts @@ -13,7 +13,6 @@ // Former goog.module ID: Blockly.PHP.all import {PhpGenerator} from './php/php_generator.js'; -import * as colour from './php/colour.js'; import * as lists from './php/lists.js'; import * as logic from './php/logic.js'; import * as loops from './php/loops.js'; @@ -33,7 +32,6 @@ export const phpGenerator = new PhpGenerator(); // Install per-block-type generator functions: const generators: typeof phpGenerator.forBlock = { - ...colour, ...lists, ...logic, ...loops, diff --git a/generators/php/colour.ts b/generators/php/colour.ts deleted file mode 100644 index eefb7cba7..000000000 --- a/generators/php/colour.ts +++ /dev/null @@ -1,102 +0,0 @@ -/** - * @license - * Copyright 2015 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @file Generating PHP for colour blocks. - */ - -// Former goog.module ID: Blockly.PHP.colour - -import type {Block} from '../../core/block.js'; -import {Order} from './php_generator.js'; -import type {PhpGenerator} from './php_generator.js'; - -export function colour_picker( - block: Block, - generator: PhpGenerator, -): [string, Order] { - // Colour picker. - const code = generator.quote_(block.getFieldValue('COLOUR')); - return [code, Order.ATOMIC]; -} - -export function colour_random( - block: Block, - generator: PhpGenerator, -): [string, Order] { - // Generate a random colour. - const functionName = generator.provideFunction_( - 'colour_random', - ` -function ${generator.FUNCTION_NAME_PLACEHOLDER_}() { - return '#' . str_pad(dechex(mt_rand(0, 0xFFFFFF)), 6, '0', STR_PAD_LEFT); -} -`, - ); - const code = functionName + '()'; - return [code, Order.FUNCTION_CALL]; -} - -export function colour_rgb( - block: Block, - generator: PhpGenerator, -): [string, Order] { - // Compose a colour from RGB components expressed as percentages. - const red = generator.valueToCode(block, 'RED', Order.NONE) || 0; - const green = generator.valueToCode(block, 'GREEN', Order.NONE) || 0; - const blue = generator.valueToCode(block, 'BLUE', Order.NONE) || 0; - const functionName = generator.provideFunction_( - 'colour_rgb', - ` -function ${generator.FUNCTION_NAME_PLACEHOLDER_}($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; -} -`, - ); - const code = functionName + '(' + red + ', ' + green + ', ' + blue + ')'; - return [code, Order.FUNCTION_CALL]; -} - -export function colour_blend( - block: Block, - generator: PhpGenerator, -): [string, Order] { - // Blend two colours together. - const c1 = generator.valueToCode(block, 'COLOUR1', Order.NONE) || "'#000000'"; - const c2 = generator.valueToCode(block, 'COLOUR2', Order.NONE) || "'#000000'"; - const ratio = generator.valueToCode(block, 'RATIO', Order.NONE) || 0.5; - const functionName = generator.provideFunction_( - 'colour_blend', - ` -function ${generator.FUNCTION_NAME_PLACEHOLDER_}($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; -} -`, - ); - const code = functionName + '(' + c1 + ', ' + c2 + ', ' + ratio + ')'; - return [code, Order.FUNCTION_CALL]; -} diff --git a/generators/php/text.ts b/generators/php/text.ts index f2fa69fae..811e0251f 100644 --- a/generators/php/text.ts +++ b/generators/php/text.ts @@ -21,16 +21,6 @@ export function text(block: Block, generator: PhpGenerator): [string, Order] { return [code, Order.ATOMIC]; } -export function text_multiline( - block: Block, - generator: PhpGenerator, -): [string, Order] { - // Text value. - const code = generator.multiline_quote_(block.getFieldValue('TEXT')); - const order = code.indexOf('.') !== -1 ? Order.STRING_CONCAT : Order.ATOMIC; - return [code, order]; -} - export function text_join( block: Block, generator: PhpGenerator, diff --git a/generators/python.ts b/generators/python.ts index 08ab10e81..d7b505763 100644 --- a/generators/python.ts +++ b/generators/python.ts @@ -13,7 +13,6 @@ // Former goog.module ID: Blockly.Python.all import {PythonGenerator} from './python/python_generator.js'; -import * as colour from './python/colour.js'; import * as lists from './python/lists.js'; import * as logic from './python/logic.js'; import * as loops from './python/loops.js'; @@ -38,7 +37,6 @@ pythonGenerator.addReservedWords('math,random,Number'); // Install per-block-type generator functions: // Install per-block-type generator functions: const generators: typeof pythonGenerator.forBlock = { - ...colour, ...lists, ...logic, ...loops, diff --git a/generators/python/colour.ts b/generators/python/colour.ts deleted file mode 100644 index 729d87cf5..000000000 --- a/generators/python/colour.ts +++ /dev/null @@ -1,88 +0,0 @@ -/** - * @license - * Copyright 2012 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @file Generating Python for colour blocks. - */ - -// Former goog.module ID: Blockly.Python.colour - -import type {Block} from '../../core/block.js'; -import type {PythonGenerator} from './python_generator.js'; -import {Order} from './python_generator.js'; - -export function colour_picker( - block: Block, - generator: PythonGenerator, -): [string, Order] { - // Colour picker. - const code = generator.quote_(block.getFieldValue('COLOUR')); - return [code, Order.ATOMIC]; -} - -export function colour_random( - block: Block, - generator: PythonGenerator, -): [string, Order] { - // Generate a random colour. - // TODO(#7600): find better approach than casting to any to override - // CodeGenerator declaring .definitions protected. - (generator as AnyDuringMigration).definitions_['import_random'] = - 'import random'; - const code = "'#%06x' % random.randint(0, 2**24 - 1)"; - return [code, Order.FUNCTION_CALL]; -} - -export function colour_rgb( - block: Block, - generator: PythonGenerator, -): [string, Order] { - // Compose a colour from RGB components expressed as percentages. - const functionName = generator.provideFunction_( - 'colour_rgb', - ` -def ${generator.FUNCTION_NAME_PLACEHOLDER_}(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) -`, - ); - const r = generator.valueToCode(block, 'RED', Order.NONE) || 0; - const g = generator.valueToCode(block, 'GREEN', Order.NONE) || 0; - const b = generator.valueToCode(block, 'BLUE', Order.NONE) || 0; - const code = functionName + '(' + r + ', ' + g + ', ' + b + ')'; - return [code, Order.FUNCTION_CALL]; -} - -export function colour_blend( - block: Block, - generator: PythonGenerator, -): [string, Order] { - // Blend two colours together. - const functionName = generator.provideFunction_( - 'colour_blend', - ` -def ${generator.FUNCTION_NAME_PLACEHOLDER_}(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) -`, - ); - const colour1 = - generator.valueToCode(block, 'COLOUR1', Order.NONE) || "'#000000'"; - const colour2 = - generator.valueToCode(block, 'COLOUR2', Order.NONE) || "'#000000'"; - const ratio = generator.valueToCode(block, 'RATIO', Order.NONE) || 0; - const code = - functionName + '(' + colour1 + ', ' + colour2 + ', ' + ratio + ')'; - return [code, Order.FUNCTION_CALL]; -} diff --git a/generators/python/text.ts b/generators/python/text.ts index 5d93e91ed..e9154da83 100644 --- a/generators/python/text.ts +++ b/generators/python/text.ts @@ -26,16 +26,6 @@ export function text( return [code, Order.ATOMIC]; } -export function text_multiline( - block: Block, - generator: PythonGenerator, -): [string, Order] { - // Text value. - const code = generator.multiline_quote_(block.getFieldValue('TEXT')); - const order = code.indexOf('+') !== -1 ? Order.ADDITIVE : Order.ATOMIC; - return [code, order]; -} - /** * Regular expression to detect a single-quoted string literal. */ diff --git a/tests/generators/colour.xml b/tests/generators/colour.xml deleted file mode 100644 index 05a9dee48..000000000 --- a/tests/generators/colour.xml +++ /dev/null @@ -1,318 +0,0 @@ - - - test colour picker - Describe this function... - - - - - static colour - - - - - #ff6600 - - - - - #ff6600 - - - - - - - test rgb - Describe this function... - - - - - from rgb - - - - - - - 100 - - - - - 40 - - - - - 0 - - - - - - - #ff6600 - - - - - - - Colour - - - - - - - - - - - - - - - - - - - - - - - test colour random - Describe this function... - - - - - 100 - - - - - item - - - - - - - - - - - length of random colour string: - - - - - item - - - - - - - - - item - - - - - - - 7 - - - - - - - - - - format of random colour string: - - - - - item - - - - - - - - FIRST - - - item - - - - - - - # - - - - - i - - - 1 - - - - - 6 - - - - - TRUE - - - - - - contents of random colour string: - - - - - item - - - - - at index: - - - - - - - i - - - - - - - - - NEQ - - - - - -1 - - - - - - - FIRST - - - abcdefABDEF0123456789 - - - - - - FROM_START - - - item - - - - - - - i - - - - - - - - - - - - - - - - - - - - - - - - - test blend - Describe this function... - - - - - blend - - - - - - - #ff0000 - - - - - - - 100 - - - - - 40 - - - - - 0 - - - - - - - 0.4 - - - - - - - #ff2900 - - - - - - diff --git a/tests/generators/golden/generated.dart b/tests/generators/golden/generated.dart index a47886588..f565ecae1 100644 --- a/tests/generators/golden/generated.dart +++ b/tests/generators/golden/generated.dart @@ -1,6 +1,6 @@ import 'dart:math' as Math; -var unittestResults, test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy; +var unittestResults, test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy; String unittest_report() { // Create test report. @@ -941,20 +941,6 @@ void test_replace() { unittest_assertequals(''.replaceAll('a', 'chicken'), '', 'empty source'); } -/// Tests the "multiline" block. -void test_multiline() { - unittest_assertequals('', '', 'no text'); - unittest_assertequals('Google', 'Google', 'simple'); - unittest_assertequals('paragraph' + '\n' + - 'with newlines' + '\n' + - 'yup', 'paragraph' + '\n' + - 'with newlines' + '\n' + - 'yup', 'no compile error with newlines'); - unittest_assertequals(text_count('bark bark' + '\n' + - 'bark bark bark' + '\n' + - 'bark bark bark bark', 'bark'), 9, 'count with newlines'); -} - /// Checks that the number of calls is one in order /// to confirm that a function was only called once. void check_number_of_calls2(test_name) { @@ -1426,80 +1412,6 @@ void test_lists_reverse() { unittest_assertequals(new List.from(list.reversed), [], 'empty list'); } -/// Describe this function... -void test_colour_picker() { - unittest_assertequals('#ff6600', '#ff6600', 'static colour'); -} - -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'; -} - -/// Describe this function... -void test_rgb() { - unittest_assertequals(colour_rgb(100, 40, 0), '#ff6600', 'from rgb'); -} - -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)]}'; -} - -/// Describe this function... -void test_colour_random() { - for (int count4 = 0; count4 < 100; count4++) { - item = colour_random(); - unittest_assertequals(item.length, 7, ['length of random colour string: ',item].join()); - unittest_assertequals(item[0], '#', ['format of random colour string: ',item].join()); - for (i = 1; i <= 6; i++) { - unittest_assertequals(0 != 'abcdefABDEF0123456789'.indexOf(item[((i + 1) - 1)]) + 1, true, ['contents of random colour string: ',item,' at index: ',i + 1].join()); - } - } -} - -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'; -} - -/// Describe this function... -void test_blend() { - unittest_assertequals(colour_blend('#ff0000', colour_rgb(100, 40, 0), 0.4), '#ff2900', 'blend'); -} - /// Describe this function... void test_procedure() { procedure_1(8, 2); @@ -1642,7 +1554,6 @@ main() { test_count_text(); test_text_reverse(); test_replace(); - test_multiline(); print(unittest_report()); unittestResults = null; @@ -1671,15 +1582,6 @@ main() { print(unittest_report()); unittestResults = null; - unittestResults = []; - print('\n====================\n\nRunning suite: Colour'); - test_colour_picker(); - test_blend(); - test_rgb(); - test_colour_random(); - print(unittest_report()); - unittestResults = null; - unittestResults = []; print('\n====================\n\nRunning suite: Variables'); item = 123; diff --git a/tests/generators/golden/generated.js b/tests/generators/golden/generated.js index f8ac5651a..7601cbdda 100644 --- a/tests/generators/golden/generated.js +++ b/tests/generators/golden/generated.js @@ -1,6 +1,6 @@ 'use strict'; -var unittestResults, test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy; +var unittestResults, test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy; function unittest_report() { // Create test report. @@ -920,20 +920,6 @@ function test_replace() { assertEquals(textReplace('', 'a', 'chicken'), '', 'empty source'); } -// Tests the "multiline" block. -function test_multiline() { - assertEquals('', '', 'no text'); - assertEquals('Google', 'Google', 'simple'); - assertEquals('paragraph' + '\n' + - 'with newlines' + '\n' + - 'yup', 'paragraph' + '\n' + - 'with newlines' + '\n' + - 'yup', 'no compile error with newlines'); - assertEquals(textCount('bark bark' + '\n' + - 'bark bark bark' + '\n' + - 'bark bark bark bark', 'bark'), 9, 'count with newlines'); -} - // Checks that the number of calls is one in order // to confirm that a function was only called once. function check_number_of_calls2(test_name) { @@ -1376,65 +1362,6 @@ function test_lists_reverse() { assertEquals(list.slice().reverse(), [], 'empty list'); } -// Describe this function... -function test_colour_picker() { - assertEquals('#ff6600', '#ff6600', 'static colour'); -} - -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; -} - -// Describe this function... -function test_rgb() { - assertEquals(colourRgb(100, 40, 0), '#ff6600', 'from rgb'); -} - -function colourRandom() { - var num = Math.floor(Math.random() * Math.pow(2, 24)); - return '#' + ('00000' + num.toString(16)).substr(-6); -} - -// Describe this function... -function test_colour_random() { - for (var count4 = 0; count4 < 100; count4++) { - item = colourRandom(); - assertEquals(item.length, 7, 'length of random colour string: ' + String(item)); - assertEquals(item.charAt(0), '#', 'format of random colour string: ' + String(item)); - for (i = 1; i <= 6; i++) { - assertEquals(0 != 'abcdefABDEF0123456789'.indexOf(item.charAt(((i + 1) - 1))) + 1, true, ['contents of random colour string: ',item,' at index: ',i + 1].join('')); - } - } -} - -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; -} - -// Describe this function... -function test_blend() { - assertEquals(colourBlend('#ff0000', colourRgb(100, 40, 0), 0.4), '#ff2900', 'blend'); -} - // Describe this function... function test_procedure() { procedure_1(8, 2); @@ -1576,7 +1503,6 @@ test_trim(); test_count_text(); test_text_reverse(); test_replace(); -test_multiline(); console.log(unittest_report()); unittestResults = null; @@ -1605,15 +1531,6 @@ test_lists_reverse(); console.log(unittest_report()); unittestResults = null; -unittestResults = []; -console.log('\n====================\n\nRunning suite: Colour') -test_colour_picker(); -test_blend(); -test_rgb(); -test_colour_random(); -console.log(unittest_report()); -unittestResults = null; - unittestResults = []; console.log('\n====================\n\nRunning suite: Variables') item = 123; diff --git a/tests/generators/golden/generated.lua b/tests/generators/golden/generated.lua index 085a76070..63dbe0af4 100644 --- a/tests/generators/golden/generated.lua +++ b/tests/generators/golden/generated.lua @@ -1015,21 +1015,6 @@ function test_replace() end --- Tests the "multiline" block. -function test_multiline() - assertEquals('', '', 'no text') - assertEquals('Google', 'Google', 'simple') - assertEquals('paragraph' .. '\n' .. - 'with newlines' .. '\n' .. - 'yup', 'paragraph' .. '\n' .. - 'with newlines' .. '\n' .. - 'yup', 'no compile error with newlines') - assertEquals(text_count('bark bark' .. '\n' .. - 'bark bark bark' .. '\n' .. - 'bark bark bark bark', 'bark'), 9, 'count with newlines') -end - - -- Checks that the number of calls is one in order -- to confirm that a function was only called once. function check_number_of_calls2(test_name) @@ -1645,58 +1630,6 @@ function test_lists_reverse() end --- Describe this function... -function test_colour_picker() - assertEquals('#ff6600', '#ff6600', 'static colour') -end - - -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 - --- Describe this function... -function test_rgb() - assertEquals(colour_rgb(100, 40, 0), '#ff6600', 'from rgb') -end - - --- Describe this function... -function test_colour_random() - for count4 = 1, 100 do - item = string.format("#%06x", math.random(0, 2^24 - 1)) - assertEquals(#item, 7, 'length of random colour string: ' .. item) - assertEquals(string.sub(item, 1, 1), '#', 'format of random colour string: ' .. item) - for i = 1, 6, 1 do - assertEquals(0 ~= firstIndexOf('abcdefABDEF0123456789', text_char_at(item, i + 1)), true, table.concat({'contents of random colour string: ', item, ' at index: ', i + 1})) - end - end -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 - --- Describe this function... -function test_blend() - assertEquals(colour_blend('#ff0000', colour_rgb(100, 40, 0), 0.4), '#ff2900', 'blend') -end - - -- Describe this function... function test_procedure() procedure_1(8, 2) @@ -1846,7 +1779,6 @@ test_trim() test_count_text() test_text_reverse() test_replace() -test_multiline() print(unittest_report()) unittestResults = nil @@ -1875,15 +1807,6 @@ test_lists_reverse() print(unittest_report()) unittestResults = nil -unittestResults = {} -print('\n====================\n\nRunning suite: Colour') -test_colour_picker() -test_blend() -test_rgb() -test_colour_random() -print(unittest_report()) -unittestResults = nil - unittestResults = {} print('\n====================\n\nRunning suite: Variables') item = 123 diff --git a/tests/generators/golden/generated.php b/tests/generators/golden/generated.php index 4a16b6f5c..d497f1a70 100644 --- a/tests/generators/golden/generated.php +++ b/tests/generators/golden/generated.php @@ -53,7 +53,7 @@ function unittest_fail($message) { // Describe this function... function test_if() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; if (false) { unittest_fail('if false'); } @@ -91,7 +91,7 @@ function test_if() { // Describe this function... function test_ifelse() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $ok = false; if (true) { $ok = true; @@ -110,7 +110,7 @@ function test_ifelse() { // Describe this function... function test_equalities() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; assertEquals(2 == 2, true, 'Equal yes'); assertEquals(3 == 4, false, 'Equal no'); assertEquals(5 != 6, true, 'Not equal yes'); @@ -127,7 +127,7 @@ function test_equalities() { // Describe this function... function test_and() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; assertEquals(true && true, true, 'And true/true'); assertEquals(false && true, false, 'And false/true'); assertEquals(true && false, false, 'And true/false'); @@ -136,7 +136,7 @@ function test_and() { // Describe this function... function test_or() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; assertEquals(true || true, true, 'Or true/true'); assertEquals(false || true, true, 'Or false/true'); assertEquals(true || false, true, 'Or true/false'); @@ -145,14 +145,14 @@ function test_or() { // Describe this function... function test_ternary() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; assertEquals(true ? 42 : 99, 42, 'if true'); assertEquals(false ? 42 : 99, 99, 'if true'); } // Describe this function... function test_foreach() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $log = ''; foreach (array('a', 'b', 'c') as $x) { $log .= $x; @@ -162,7 +162,7 @@ function test_foreach() { // Describe this function... function test_repeat() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $count = 0; for ($count2 = 0; $count2 < 10; $count2++) { $count += 1; @@ -172,7 +172,7 @@ function test_repeat() { // Describe this function... function test_while() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; while (false) { unittest_fail('while 0'); } @@ -193,7 +193,7 @@ function test_while() { // Describe this function... function test_repeat_ext() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $count = 0; for ($count3 = 0; $count3 < 10; $count3++) { $count += 1; @@ -203,7 +203,7 @@ function test_repeat_ext() { // Describe this function... function test_count_by() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $log = ''; for ($x = 1; $x <= 8; $x += 2) { $log .= $x; @@ -256,7 +256,7 @@ function test_count_by() { // Describe this function... function test_count_loops() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $log = ''; for ($x = 1; $x <= 8; $x++) { $log .= $x; @@ -293,7 +293,7 @@ function test_count_loops() { // Describe this function... function test_continue() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $log = ''; $count = 0; while ($count != 8) { @@ -334,7 +334,7 @@ function test_continue() { // Describe this function... function test_break() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $count = 1; while ($count != 10) { if ($count == 5) { @@ -371,7 +371,7 @@ function test_break() { // Tests the "single" block. function test_single() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; assertEquals(sqrt(25), 5, 'sqrt'); assertEquals(abs(-25), 25, 'abs'); assertEquals(-(-25), 25, 'negate'); @@ -384,7 +384,7 @@ function test_single() { // Tests the "arithmetic" block for all operations and checks // parenthesis are properly generated for different orders. function test_arithmetic() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; assertEquals(1 + 2, 3, 'add'); assertEquals(1 - 2, -1, 'subtract'); assertEquals(1 - (0 + 2), -1, 'subtract order with add'); @@ -399,7 +399,7 @@ function test_arithmetic() { // Tests the "trig" block. function test_trig() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; assertEquals(sin(90 / 180 * pi()), 1, 'sin'); assertEquals(cos(180 / 180 * pi()), -1, 'cos'); assertEquals(tan(0 / 180 * pi()), 0, 'tan'); @@ -410,7 +410,7 @@ function test_trig() { // Tests the "constant" blocks. function test_constant() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; assertEquals(floor(M_PI * 1000), 3141, 'const pi'); assertEquals(floor(M_E * 1000), 2718, 'const e'); assertEquals(floor(((1 + sqrt(5)) / 2) * 1000), 1618, 'const golden'); @@ -440,7 +440,7 @@ function math_isPrime($n) { // Tests the "number property" blocks. function test_number_properties() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; assertEquals(42 % 2 == 0, true, 'even'); assertEquals(42.1 % 2 == 1, false, 'odd'); assertEquals(math_isPrime(5), true, 'prime 5'); @@ -458,7 +458,7 @@ function test_number_properties() { // Tests the "round" block. function test_round() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; assertEquals(round(42.42), 42, 'round'); assertEquals(ceil(-42.42), -42, 'round up'); assertEquals(floor(42.42), 42, 'round down'); @@ -466,7 +466,7 @@ function test_round() { // Tests the "change" block. function test_change() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $varToChange = 100; $varToChange += 42; assertEquals($varToChange, 142, 'change'); @@ -512,7 +512,7 @@ function indexOf($haystack, $needle) { // Tests the "list operation" blocks. function test_operations_on_list() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; assertEquals(array_sum((array(3, 4, 5))), 12, 'sum'); assertEquals(min((array(3, 4, 5))), 3, 'min'); assertEquals(max((array(3, 4, 5))), 5, 'max'); @@ -526,13 +526,13 @@ function test_operations_on_list() { // Tests the "mod" block. function test_mod() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; assertEquals(42 % 5, 2, 'mod'); } // Tests the "constrain" block. function test_constraint() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; assertEquals(min(max(100, 0), 42), 42, 'constraint'); } @@ -545,7 +545,7 @@ function math_random_int($a, $b) { // Tests the "random integer" block. function test_random_integer() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $rand = math_random_int(5, 10); assertEquals($rand >= 5 && $rand <= 10, true, 'randRange'); assertEquals(is_int($rand), true, 'randInteger'); @@ -553,14 +553,14 @@ function test_random_integer() { // Tests the "random fraction" block. function test_random_fraction() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $rand = (float)rand()/(float)getrandmax(); assertEquals($rand >= 0 && $rand <= 1, true, 'randFloat'); } // Describe this function... function test_atan2() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; assertEquals(atan2(5, -5) / pi() * 180, 135, 'atan2'); assertEquals(atan2(-12, 0) / pi() * 180, -90, 'atan2'); } @@ -568,14 +568,14 @@ function test_atan2() { // Checks that the number of calls is one in order // to confirm that a function was only called once. function check_number_of_calls($test_name) { - global $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $test_name .= 'number of calls'; assertEquals($number_of_calls, 1, $test_name); } // Tests the "create text with" block with varying number of inputs. function test_create_text() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; assertEquals('', '', 'no text'); assertEquals('Hello', 'Hello', 'create single'); assertEquals(-1, '-1', 'create single number'); @@ -587,13 +587,13 @@ function test_create_text() { // Creates an empty string for use with the empty test. function get_empty() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; return ''; } // Tests the "is empty" block". function test_empty_text() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; assertEquals(empty('Google'), false, 'not empty'); assertEquals(empty(''), true, 'empty'); assertEquals(empty(get_empty()), true, 'empty complex'); @@ -609,7 +609,7 @@ function length($value) { // Tests the "length" block. function test_text_length() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; assertEquals(length(''), 0, 'zero length'); assertEquals(length('Google'), 6, 'non-zero length'); assertEquals(length(true ? 'car' : null), 3, 'length order'); @@ -617,7 +617,7 @@ function test_text_length() { // Tests the "append text" block with different types of parameters. function test_append() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $item = 'Miserable'; $item .= 'Failure'; assertEquals($item, 'MiserableFailure', 'append text'); @@ -641,7 +641,7 @@ function text_lastIndexOf($text, $search) { // Tests the "find" block with a variable. function test_find_text_simple() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $text = 'Banana'; assertEquals(text_indexOf($text, 'an'), 2, 'find first simple'); assertEquals(text_lastIndexOf($text, 'an'), 4, 'find last simple'); @@ -650,14 +650,14 @@ function test_find_text_simple() { // Creates a string for use with the find test. function get_fruit() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $number_of_calls += 1; return 'Banana'; } // Tests the "find" block with a function call. function test_find_text_complex() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $number_of_calls = 0; assertEquals(text_indexOf(get_fruit(), 'an'), 2, 'find first complex'); check_number_of_calls('find first complex'); @@ -684,7 +684,7 @@ function text_random_letter($text) { // Tests the "get letter" block with a variable. function test_get_text_simple() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $text = 'Blockly'; assertEquals(substr($text, 0, 1), 'B', 'get first simple'); assertEquals(substr($text, -1), 'y', 'get last simple'); @@ -698,14 +698,14 @@ function test_get_text_simple() { // Creates a string for use with the get test. function get_Blockly() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $number_of_calls += 1; return 'Blockly'; } // Tests the "get letter" block with a function call. function test_get_text_complex() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $text = 'Blockly'; $number_of_calls = 0; assertEquals(substr(get_Blockly(), 0, 1), 'B', 'get first complex'); @@ -742,7 +742,7 @@ function test_get_text_complex() { // Creates a string for use with the substring test. function get_numbers() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $number_of_calls += 1; return '123456789'; } @@ -770,7 +770,7 @@ function text_get_substring($text, $where1, $at1, $where2, $at2) { // Tests the "get substring" block with a variable. function test_substring_simple() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $text = '123456789'; assertEquals(text_get_substring($text, 'FROM_START', 1, 'FROM_START', 2), '23', 'substring # simple'); assertEquals(text_get_substring($text, 'FROM_START', ((true ? 2 : null) - 1), 'FROM_START', ((true ? 3 : null) - 1)), '23', 'substring # simple order'); @@ -792,7 +792,7 @@ function test_substring_simple() { // Tests the "get substring" block with a function call. function test_substring_complex() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $number_of_calls = 0; assertEquals(text_get_substring(get_numbers(), 'FROM_START', 1, 'FROM_START', 2), '23', 'substring # complex'); check_number_of_calls('substring # complex'); @@ -841,7 +841,7 @@ function test_substring_complex() { // Tests the "change casing" block. function test_case() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $text = 'Hello World'; assertEquals(strtoupper($text), 'HELLO WORLD', 'uppercase'); assertEquals(strtoupper(true ? $text : null), 'HELLO WORLD', 'uppercase order'); @@ -855,7 +855,7 @@ function test_case() { // Tests the "trim" block. function test_trim() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $text = ' abc def '; assertEquals(trim($text), 'abc def', 'trim both'); assertEquals(trim(true ? $text : null), 'abc def', 'trim both order'); @@ -867,7 +867,7 @@ function test_trim() { // Tests the "trim" block. function test_count_text() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $text = 'woolloomooloo'; assertEquals(strlen('o') === 0 ? strlen($text) + 1 : substr_count($text, 'o'), 8, 'len 1'); assertEquals(strlen('oo') === 0 ? strlen($text) + 1 : substr_count($text, 'oo'), 4, 'len 2'); @@ -880,7 +880,7 @@ function test_count_text() { // Tests the "trim" block. function test_text_reverse() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; assertEquals(strrev(''), '', 'empty string'); assertEquals(strrev('a'), 'a', 'len 1'); assertEquals(strrev('ab'), 'ba', 'len 2'); @@ -889,7 +889,7 @@ function test_text_reverse() { // Tests the "trim" block. function test_replace() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; assertEquals(str_replace('oo', '123', 'woolloomooloo'), 'w123ll123m123l123', 'replace all instances 1'); assertEquals(str_replace('.oo', 'X', 'woolloomooloo'), 'woolloomooloo', 'literal string replacement'); assertEquals(str_replace('abc', 'X', 'woolloomooloo'), 'woolloomooloo', 'not found'); @@ -899,27 +899,10 @@ function test_replace() { assertEquals(str_replace('a', 'chicken', ''), '', 'empty source'); } -// Tests the "multiline" block. -function test_multiline() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; - assertEquals('', '', 'no text'); - assertEquals('Google', 'Google', 'simple'); - assertEquals('paragraph' . "\n" . - 'with newlines' . "\n" . - 'yup', 'paragraph' . "\n" . - 'with newlines' . "\n" . - 'yup', 'no compile error with newlines'); - assertEquals(strlen('bark') === 0 ? strlen('bark bark' . "\n" . - 'bark bark bark' . "\n" . - 'bark bark bark bark') + 1 : substr_count('bark bark' . "\n" . - 'bark bark bark' . "\n" . - 'bark bark bark bark', 'bark'), 9, 'count with newlines'); -} - // Checks that the number of calls is one in order // to confirm that a function was only called once. function check_number_of_calls2($test_name) { - global $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $test_name .= 'number of calls'; assertEquals($number_of_calls, 1, $test_name); } @@ -934,7 +917,7 @@ function lists_repeat($value, $count) { // Tests the "create list with" and "create empty list" blocks. function test_create_lists() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; assertEquals(array(), array(), 'create empty'); assertEquals(array(true, 'love'), array(true, 'love'), 'create items'); assertEquals(lists_repeat('Eject', 3), array('Eject', 'Eject', 'Eject'), 'create repeated'); @@ -943,13 +926,13 @@ function test_create_lists() { // Creates an empty list for use with the empty test. function get_empty_list() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; return array(); } // Tests the "is empty" block. function test_lists_empty() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; assertEquals(empty((array(0))), false, 'not empty'); assertEquals(empty((array())), true, 'empty'); assertEquals(empty((get_empty_list())), true, 'empty complex'); @@ -958,7 +941,7 @@ function test_lists_empty() { // Tests the "length" block. function test_lists_length() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; assertEquals(length(array()), 0, 'zero length'); assertEquals(length(array('cat')), 1, 'one length'); assertEquals(length(array('cat', true, array())), 3, 'three length'); @@ -975,7 +958,7 @@ function lastIndexOf($haystack, $needle) { // Tests the "find" block with a variable. function test_find_lists_simple() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $list2 = array('Alice', 'Eve', 'Bob', 'Eve'); assertEquals(indexOf($list2, 'Eve'), 2, 'find first simple'); assertEquals(lastIndexOf($list2, 'Eve'), 4, 'find last simple'); @@ -984,14 +967,14 @@ function test_find_lists_simple() { // Creates a list for use with the find test. function get_names() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $number_of_calls += 1; return array('Alice', 'Eve', 'Bob', 'Eve'); } // Tests the "find" block with a function call. function test_find_lists_complex() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $number_of_calls = 0; assertEquals(indexOf(get_names(), 'Eve'), 2, 'find first complex'); check_number_of_calls('find first complex'); @@ -1018,7 +1001,7 @@ function lists_get_random_item($list) { // Tests the "get" block with a variable. function test_get_lists_simple() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $list2 = array('Kirk', 'Spock', 'McCoy'); assertEquals($list2[0], 'Kirk', 'get first simple'); assertEquals(end($list2), 'McCoy', 'get last simple'); @@ -1032,7 +1015,7 @@ function test_get_lists_simple() { // Tests the "get" block with create list call. function test_get_lists_create_list() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; assertEquals(array('Kirk', 'Spock', 'McCoy')[0], 'Kirk', 'get first create list'); assertEquals(end(array('Kirk', 'Spock', 'McCoy')), 'McCoy', 'get last simple'); assertEquals(indexOf(array('Kirk', 'Spock', 'McCoy'), lists_get_random_item(array('Kirk', 'Spock', 'McCoy'))) > 0, true, 'get random simple'); @@ -1045,14 +1028,14 @@ function test_get_lists_create_list() { // Creates a list for use with the get test. function get_star_wars() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $number_of_calls += 1; return array('Kirk', 'Spock', 'McCoy'); } // Tests the "get" block with a function call. function test_get_lists_complex() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $list2 = array('Kirk', 'Spock', 'McCoy'); $number_of_calls = 0; assertEquals(get_star_wars()[0], 'Kirk', 'get first complex'); @@ -1095,7 +1078,7 @@ function lists_get_remove_random_item(&$list) { // Tests the "get and remove" block. function test_getRemove() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $list2 = array('Kirk', 'Spock', 'McCoy'); assertEquals(array_shift($list2), 'Kirk', 'getremove first'); assertEquals($list2, array('Spock', 'McCoy'), 'getremove first list'); @@ -1135,7 +1118,7 @@ function lists_remove_random_item(&$list) { // Tests the "remove" block. function test_remove() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $list2 = array('Kirk', 'Spock', 'McCoy'); array_shift($list2); assertEquals($list2, array('Spock', 'McCoy'), 'remove first list'); @@ -1180,7 +1163,7 @@ function lists_set_from_end(&$list, $at, $value) { // Tests the "set" block. function test_set() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $list2 = array('Picard', 'Riker', 'Crusher'); $list2[0] = 'Jean-Luc'; assertEquals($list2, array('Jean-Luc', 'Riker', 'Crusher'), 'set first list'); @@ -1224,7 +1207,7 @@ function lists_insert_from_end(&$list, $at, $value) { // Tests the "insert" block. function test_insert() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $list2 = array('Picard', 'Riker', 'Crusher'); array_unshift($list2, 'Data'); assertEquals($list2, array('Data', 'Picard', 'Riker', 'Crusher'), 'insert first list'); @@ -1264,7 +1247,7 @@ function test_insert() { // Tests the "get sub-list" block with a variable. function test_sublist_simple() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $list2 = array('Columbia', 'Challenger', 'Discovery', 'Atlantis', 'Endeavour'); assertEquals(array_slice($list2, 1, 2 - 1 + 1), array('Challenger', 'Discovery'), 'sublist # simple'); assertEquals(array_slice($list2, ((true ? 2 : null) - 1), ((true ? 3 : null) - 1) - ((true ? 2 : null) - 1) + 1), array('Challenger', 'Discovery'), 'sublist # simple order'); @@ -1290,7 +1273,7 @@ function test_sublist_simple() { // Creates a list for use with the sublist test. function get_space_shuttles() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $number_of_calls += 1; return array('Columbia', 'Challenger', 'Discovery', 'Atlantis', 'Endeavour'); } @@ -1318,7 +1301,7 @@ function lists_get_sublist($list, $where1, $at1, $where2, $at2) { // Tests the "get sub-list" block with a function call. function test_sublist_complex() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $number_of_calls = 0; assertEquals(array_slice(get_space_shuttles(), 1, 2 - 1 + 1), array('Challenger', 'Discovery'), 'sublist # start complex'); check_number_of_calls('sublist # start complex'); @@ -1367,7 +1350,7 @@ function test_sublist_complex() { // Tests the "join" block. function test_join() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $list2 = array('Vulcan', 'Klingon', 'Borg'); assertEquals(implode(',', $list2), 'Vulcan,Klingon,Borg', 'join'); assertEquals(implode(',', true ? $list2 : null), 'Vulcan,Klingon,Borg', 'join order'); @@ -1375,7 +1358,7 @@ function test_join() { // Tests the "split" block. function test_split() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $text = 'Vulcan,Klingon,Borg'; assertEquals(explode(',', $text), array('Vulcan', 'Klingon', 'Borg'), 'split'); assertEquals(explode(',', true ? $text : null), array('Vulcan', 'Klingon', 'Borg'), 'split order'); @@ -1398,7 +1381,7 @@ function lists_sort($list, $type, $direction) { // Tests the "alphabetic sort" block. function test_sort_alphabetic() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $list2 = array('Vulcan', 'klingon', 'Borg'); assertEquals(lists_sort($list2, "TEXT", 1), array('Borg', 'Vulcan', 'klingon'), 'sort alphabetic ascending'); assertEquals(lists_sort(true ? $list2 : null, "TEXT", 1), array('Borg', 'Vulcan', 'klingon'), 'sort alphabetic ascending order'); @@ -1406,7 +1389,7 @@ function test_sort_alphabetic() { // Tests the "alphabetic sort ignore case" block. function test_sort_ignoreCase() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $list2 = array('Vulcan', 'klingon', 'Borg'); assertEquals(lists_sort($list2, "IGNORE_CASE", 1), array('Borg', 'klingon', 'Vulcan'), 'sort ignore case ascending'); assertEquals(lists_sort(true ? $list2 : null, "IGNORE_CASE", 1), array('Borg', 'klingon', 'Vulcan'), 'sort ignore case ascending order'); @@ -1414,7 +1397,7 @@ function test_sort_ignoreCase() { // Tests the "numeric sort" block. function test_sort_numeric() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $list2 = array(8, 18, -1); assertEquals(lists_sort($list2, "NUMERIC", -1), array(18, 8, -1), 'sort numeric descending'); assertEquals(lists_sort(true ? $list2 : null, "NUMERIC", -1), array(18, 8, -1), 'sort numeric descending order'); @@ -1422,7 +1405,7 @@ function test_sort_numeric() { // Tests the "list reverse" block. function test_lists_reverse() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $list2 = array(8, 18, -1, 64); assertEquals(array_reverse($list2), array(64, -1, 18, 8), 'reverse a copy'); assertEquals($list2, array(8, 18, -1, 64), 'reverse a copy original'); @@ -1430,73 +1413,9 @@ function test_lists_reverse() { assertEquals(array_reverse($list2), array(), 'empty list'); } -// Describe this function... -function test_colour_picker() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; - assertEquals('#ff6600', '#ff6600', 'static colour'); -} - -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; -} - -// Describe this function... -function test_rgb() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; - assertEquals(colour_rgb(100, 40, 0), '#ff6600', 'from rgb'); -} - -function colour_random() { - return '#' . str_pad(dechex(mt_rand(0, 0xFFFFFF)), 6, '0', STR_PAD_LEFT); -} - -// Describe this function... -function test_colour_random() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; - for ($count4 = 0; $count4 < 100; $count4++) { - $item = colour_random(); - assertEquals(length($item), 7, 'length of random colour string: ' . $item); - assertEquals(substr($item, 0, 1), '#', 'format of random colour string: ' . $item); - for ($i = 1; $i <= 6; $i++) { - assertEquals(0 != text_indexOf('abcdefABDEF0123456789', substr($item, (($i + 1) - 1), 1)), true, implode('', array('contents of random colour string: ',$item,' at index: ',$i + 1))); - } - } -} - -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; -} - -// Describe this function... -function test_blend() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; - assertEquals(colour_blend('#ff0000', colour_rgb(100, 40, 0), 0.4), '#ff2900', 'blend'); -} - // Describe this function... function test_procedure() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; procedure_1(8, 2); assertEquals($proc_z, 4, 'procedure with global'); $proc_w = false; @@ -1509,13 +1428,13 @@ function test_procedure() { // Describe this function... function procedure_1($proc_x, $proc_y) { - global $test_name, $naked, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $proc_z = $proc_x / $proc_y; } // Describe this function... function procedure_2($proc_x) { - global $test_name, $naked, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; if ($proc_x) { return; } @@ -1524,7 +1443,7 @@ function procedure_2($proc_x) { // Describe this function... function test_function() { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; assertEquals(function_1(2, 3), -1, 'function with arguments'); assertEquals($func_z, 'side effect', 'function with side effect'); $func_a = 'unchanged'; @@ -1537,21 +1456,21 @@ function test_function() { // Describe this function... function function_1($func_x, $func_y) { - global $test_name, $naked, $proc_x, $proc_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_a, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $func_z = 'side effect'; return $func_x - $func_y; } // Describe this function... function function_2($func_a) { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; $func_a += 1; return $func_a . $func_c; } // Describe this function... function function_3($func_a) { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $n, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; if ($func_a) { return true; } @@ -1560,7 +1479,7 @@ function function_3($func_a) { // Describe this function... function recurse($n) { - global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $i, $loglist, $changing_list, $list_copy, $unittestResults; + global $test_name, $naked, $proc_x, $proc_y, $func_x, $func_y, $func_a, $ok, $log, $count, $varToChange, $rand, $item, $text, $number_of_calls, $list2, $proc_z, $func_z, $x, $proc_w, $func_c, $if2, $loglist, $changing_list, $list_copy, $unittestResults; if ($n > 0) { $text = implode('', array(recurse($n - 1),$n,recurse($n - 1))); } else { @@ -1643,7 +1562,6 @@ test_trim(); test_count_text(); test_text_reverse(); test_replace(); -test_multiline(); print(unittest_report()); $unittestResults = null; @@ -1672,15 +1590,6 @@ test_lists_reverse(); print(unittest_report()); $unittestResults = null; -$unittestResults = array(); -print("\n====================\n\nRunning suite: Colour\n"); -test_colour_picker(); -test_blend(); -test_rgb(); -test_colour_random(); -print(unittest_report()); -$unittestResults = null; - $unittestResults = array(); print("\n====================\n\nRunning suite: Variables\n"); $item = 123; diff --git a/tests/generators/golden/generated.py b/tests/generators/golden/generated.py index e4375dbbc..bafa0983b 100644 --- a/tests/generators/golden/generated.py +++ b/tests/generators/golden/generated.py @@ -27,7 +27,6 @@ x = None proc_w = None func_c = None if2 = None -i = None loglist = None changing_list = None list_copy = None @@ -73,7 +72,7 @@ def fail(message): # Describe this function... def test_if(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults if False: fail('if false') ok = False @@ -105,7 +104,7 @@ def test_if(): # Describe this function... def test_ifelse(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults ok = False if True: ok = True @@ -121,7 +120,7 @@ def test_ifelse(): # Describe this function... def test_equalities(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults assertEquals(2 == 2, True, 'Equal yes') assertEquals(3 == 4, False, 'Equal no') assertEquals(5 != 6, True, 'Not equal yes') @@ -137,7 +136,7 @@ def test_equalities(): # Describe this function... def test_and(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults assertEquals(True and True, True, 'And true/true') assertEquals(False and True, False, 'And false/true') assertEquals(True and False, False, 'And true/false') @@ -145,7 +144,7 @@ def test_and(): # Describe this function... def test_or(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults assertEquals(True or True, True, 'Or true/true') assertEquals(False or True, True, 'Or false/true') assertEquals(True or False, True, 'Or true/false') @@ -153,13 +152,13 @@ def test_or(): # Describe this function... def test_ternary(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults assertEquals(42 if True else 99, 42, 'if true') assertEquals(42 if False else 99, 99, 'if true') # Describe this function... def test_foreach(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults log = '' for x in ['a', 'b', 'c']: log = str(log) + str(x) @@ -167,7 +166,7 @@ def test_foreach(): # Describe this function... def test_repeat(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults count = 0 for count2 in range(10): count = (count if isinstance(count, Number) else 0) + 1 @@ -175,7 +174,7 @@ def test_repeat(): # Describe this function... def test_while(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults while False: fail('while 0') while not True: @@ -191,7 +190,7 @@ def test_while(): # Describe this function... def test_repeat_ext(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults count = 0 for count3 in range(10): count = (count if isinstance(count, Number) else 0) + 1 @@ -209,7 +208,7 @@ def downRange(start, stop, step): # Describe this function... def test_count_by(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults log = '' for x in range(1, 9, 2): log = str(log) + str(x) @@ -245,7 +244,7 @@ def test_count_by(): # Describe this function... def test_count_loops(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults log = '' for x in range(1, 9): log = str(log) + str(x) @@ -269,7 +268,7 @@ def test_count_loops(): # Describe this function... def test_continue(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults log = '' count = 0 while count != 8: @@ -301,7 +300,7 @@ def test_continue(): # Describe this function... def test_break(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults count = 1 while count != 10: if count == 5: @@ -329,7 +328,7 @@ def test_break(): # Tests the "single" block. def test_single(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults assertEquals(math.sqrt(25), 5, 'sqrt') assertEquals(math.fabs(-25), 25, 'abs') assertEquals(-(-25), 25, 'negate') @@ -341,7 +340,7 @@ def test_single(): # Tests the "arithmetic" block for all operations and checks # parenthesis are properly generated for different orders. def test_arithmetic(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults assertEquals(1 + 2, 3, 'add') assertEquals(1 - 2, -1, 'subtract') assertEquals(1 - (0 + 2), -1, 'subtract order with add') @@ -355,7 +354,7 @@ def test_arithmetic(): # Tests the "trig" block. def test_trig(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults assertEquals(math.sin(90 / 180.0 * math.pi), 1, 'sin') assertEquals(math.cos(180 / 180.0 * math.pi), -1, 'cos') assertEquals(math.tan(0 / 180.0 * math.pi), 0, 'tan') @@ -365,7 +364,7 @@ def test_trig(): # Tests the "constant" blocks. def test_constant(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults assertEquals(math.floor(math.pi * 1000), 3141, 'const pi') assertEquals(math.floor(math.e * 1000), 2718, 'const e') assertEquals(math.floor(((1 + math.sqrt(5)) / 2) * 1000), 1618, 'const golden') @@ -394,7 +393,7 @@ def math_isPrime(n): # Tests the "number property" blocks. def test_number_properties(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults assertEquals(42 % 2 == 0, True, 'even') assertEquals(42.1 % 2 == 1, False, 'odd') assertEquals(math_isPrime(5), True, 'prime 5') @@ -411,14 +410,14 @@ def test_number_properties(): # Tests the "round" block. def test_round(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults assertEquals(round(42.42), 42, 'round') assertEquals(math.ceil(-42.42), -42, 'round up') assertEquals(math.floor(42.42), 42, 'round down') # Tests the "change" block. def test_change(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults varToChange = 100 varToChange = (varToChange if isinstance(varToChange, Number) else 0) + 42 assertEquals(varToChange, 142, 'change') @@ -470,7 +469,7 @@ def first_index(my_list, elem): # Tests the "list operation" blocks. def test_operations_on_list(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults assertEquals(sum([3, 4, 5]), 12, 'sum') assertEquals(min([3, 4, 5]), 3, 'min') assertEquals(max([3, 4, 5]), 5, 'max') @@ -483,43 +482,43 @@ def test_operations_on_list(): # Tests the "mod" block. def test_mod(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults assertEquals(42 % 5, 2, 'mod') # Tests the "constrain" block. def test_constraint(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults assertEquals(min(max(100, 0), 42), 42, 'constraint') # Tests the "random integer" block. def test_random_integer(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults rand = random.randint(5, 10) assertEquals(rand >= 5 and rand <= 10, True, 'randRange') assertEquals(rand % 1 == 0, True, 'randInteger') # Tests the "random fraction" block. def test_random_fraction(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults rand = random.random() assertEquals(rand >= 0 and rand <= 1, True, 'randFloat') # Describe this function... def test_atan2(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults assertEquals(math.atan2(5, -5) / math.pi * 180, 135, 'atan2') assertEquals(math.atan2(-12, 0) / math.pi * 180, -90, 'atan2') # Checks that the number of calls is one in order # to confirm that a function was only called once. def check_number_of_calls(test_name): - global naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults test_name = str(test_name) + 'number of calls' assertEquals(number_of_calls, 1, test_name) # Tests the "create text with" block with varying number of inputs. def test_create_text(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults assertEquals('', '', 'no text') assertEquals('Hello', 'Hello', 'create single') assertEquals(str(-1), '-1', 'create single number') @@ -530,12 +529,12 @@ def test_create_text(): # Creates an empty string for use with the empty test. def get_empty(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults return '' # Tests the "is empty" block". def test_empty_text(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults assertEquals(not len('Google'), False, 'not empty') assertEquals(not len(''), True, 'empty') assertEquals(not len(get_empty()), True, 'empty complex') @@ -543,14 +542,14 @@ def test_empty_text(): # Tests the "length" block. def test_text_length(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults assertEquals(len(''), 0, 'zero length') assertEquals(len('Google'), 6, 'non-zero length') assertEquals(len('car' if True else None), 3, 'length order') # Tests the "append text" block with different types of parameters. def test_append(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults item = 'Miserable' item = str(item) + 'Failure' assertEquals(item, 'MiserableFailure', 'append text') @@ -563,7 +562,7 @@ def test_append(): # Tests the "find" block with a variable. def test_find_text_simple(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults text = 'Banana' assertEquals(text.find('an') + 1, 2, 'find first simple') assertEquals(text.rfind('an') + 1, 4, 'find last simple') @@ -571,13 +570,13 @@ def test_find_text_simple(): # Creates a string for use with the find test. def get_fruit(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults number_of_calls = (number_of_calls if isinstance(number_of_calls, Number) else 0) + 1 return 'Banana' # Tests the "find" block with a function call. def test_find_text_complex(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults number_of_calls = 0 assertEquals(get_fruit().find('an') + 1, 2, 'find first complex') check_number_of_calls('find first complex') @@ -603,7 +602,7 @@ def text_random_letter(text): # Tests the "get letter" block with a variable. def test_get_text_simple(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults text = 'Blockly' assertEquals(text[0], 'B', 'get first simple') assertEquals(text[-1], 'y', 'get last simple') @@ -616,13 +615,13 @@ def test_get_text_simple(): # Creates a string for use with the get test. def get_Blockly(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults number_of_calls = (number_of_calls if isinstance(number_of_calls, Number) else 0) + 1 return 'Blockly' # Tests the "get letter" block with a function call. def test_get_text_complex(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults text = 'Blockly' number_of_calls = 0 assertEquals(get_Blockly()[0], 'B', 'get first complex') @@ -658,13 +657,13 @@ def test_get_text_complex(): # Creates a string for use with the substring test. def get_numbers(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults number_of_calls = (number_of_calls if isinstance(number_of_calls, Number) else 0) + 1 return '123456789' # Tests the "get substring" block with a variable. def test_substring_simple(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults text = '123456789' assertEquals(text[1 : 3], '23', 'substring # simple') assertEquals(text[int((2 if True else None) - 1) : int(3 if True else None)], '23', 'substring # simple order') @@ -685,7 +684,7 @@ def test_substring_simple(): # Tests the "get substring" block with a function call. def test_substring_complex(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults number_of_calls = 0 assertEquals(get_numbers()[1 : 3], '23', 'substring # complex') check_number_of_calls('substring # complex') @@ -733,7 +732,7 @@ def test_substring_complex(): # Tests the "change casing" block. def test_case(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults text = 'Hello World' assertEquals(text.upper(), 'HELLO WORLD', 'uppercase') assertEquals((text if True else None).upper(), 'HELLO WORLD', 'uppercase order') @@ -746,7 +745,7 @@ def test_case(): # Tests the "trim" block. def test_trim(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults text = ' abc def ' assertEquals(text.strip(), 'abc def', 'trim both') assertEquals((text if True else None).strip(), 'abc def', 'trim both order') @@ -757,7 +756,7 @@ def test_trim(): # Tests the "trim" block. def test_count_text(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults text = 'woolloomooloo' assertEquals(text.count('o'), 8, 'len 1') assertEquals(text.count('oo'), 4, 'len 2') @@ -769,7 +768,7 @@ def test_count_text(): # Tests the "trim" block. def test_text_reverse(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults assertEquals(''[::-1], '', 'empty string') assertEquals('a'[::-1], 'a', 'len 1') assertEquals('ab'[::-1], 'ba', 'len 2') @@ -777,7 +776,7 @@ def test_text_reverse(): # Tests the "trim" block. def test_replace(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults assertEquals('woolloomooloo'.replace('oo', '123'), 'w123ll123m123l123', 'replace all instances 1') assertEquals('woolloomooloo'.replace('.oo', 'X'), 'woolloomooloo', 'literal string replacement') assertEquals('woolloomooloo'.replace('abc', 'X'), 'woolloomooloo', 'not found') @@ -786,30 +785,16 @@ def test_replace(): assertEquals('aaaaa'.replace('a', ''), '', 'empty replacement 3') assertEquals(''.replace('a', 'chicken'), '', 'empty source') -# Tests the "multiline" block. -def test_multiline(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults - assertEquals('', '', 'no text') - assertEquals('Google', 'Google', 'simple') - assertEquals('paragraph' + '\n' + - 'with newlines' + '\n' + - 'yup', 'paragraph' + '\n' + - 'with newlines' + '\n' + - 'yup', 'no compile error with newlines') - assertEquals(('bark bark' + '\n' + - 'bark bark bark' + '\n' + - 'bark bark bark bark').count('bark'), 9, 'count with newlines') - # Checks that the number of calls is one in order # to confirm that a function was only called once. def check_number_of_calls2(test_name): - global naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults test_name = str(test_name) + 'number of calls' assertEquals(number_of_calls, 1, test_name) # Tests the "create list with" and "create empty list" blocks. def test_create_lists(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults assertEquals([], [], 'create empty') assertEquals([True, 'love'], [True, 'love'], 'create items') assertEquals(['Eject'] * 3, ['Eject', 'Eject', 'Eject'], 'create repeated') @@ -817,12 +802,12 @@ def test_create_lists(): # Creates an empty list for use with the empty test. def get_empty_list(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults return [] # Tests the "is empty" block. def test_lists_empty(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults assertEquals(not len([0]), False, 'not empty') assertEquals(not len([]), True, 'empty') assertEquals(not len(get_empty_list()), True, 'empty complex') @@ -830,7 +815,7 @@ def test_lists_empty(): # Tests the "length" block. def test_lists_length(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults assertEquals(len([]), 0, 'zero length') assertEquals(len(['cat']), 1, 'one length') assertEquals(len(['cat', True, []]), 3, 'three length') @@ -843,7 +828,7 @@ def last_index(my_list, elem): # Tests the "find" block with a variable. def test_find_lists_simple(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults list2 = ['Alice', 'Eve', 'Bob', 'Eve'] assertEquals(first_index(list2, 'Eve'), 2, 'find first simple') assertEquals(last_index(list2, 'Eve'), 4, 'find last simple') @@ -851,13 +836,13 @@ def test_find_lists_simple(): # Creates a list for use with the find test. def get_names(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults number_of_calls = (number_of_calls if isinstance(number_of_calls, Number) else 0) + 1 return ['Alice', 'Eve', 'Bob', 'Eve'] # Tests the "find" block with a function call. def test_find_lists_complex(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults number_of_calls = 0 assertEquals(first_index(get_names(), 'Eve'), 2, 'find first complex') check_number_of_calls('find first complex') @@ -879,7 +864,7 @@ def test_find_lists_complex(): # Tests the "get" block with a variable. def test_get_lists_simple(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults list2 = ['Kirk', 'Spock', 'McCoy'] assertEquals(list2[0], 'Kirk', 'get first simple') assertEquals(list2[-1], 'McCoy', 'get last simple') @@ -892,7 +877,7 @@ def test_get_lists_simple(): # Tests the "get" block with create list call. def test_get_lists_create_list(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults assertEquals(['Kirk', 'Spock', 'McCoy'][0], 'Kirk', 'get first create list') assertEquals(['Kirk', 'Spock', 'McCoy'][-1], 'McCoy', 'get last simple') assertEquals(first_index(['Kirk', 'Spock', 'McCoy'], random.choice(['Kirk', 'Spock', 'McCoy'])) > 0, True, 'get random simple') @@ -904,13 +889,13 @@ def test_get_lists_create_list(): # Creates a list for use with the get test. def get_star_wars(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults number_of_calls = (number_of_calls if isinstance(number_of_calls, Number) else 0) + 1 return ['Kirk', 'Spock', 'McCoy'] # Tests the "get" block with a function call. def test_get_lists_complex(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults list2 = ['Kirk', 'Spock', 'McCoy'] number_of_calls = 0 assertEquals(get_star_wars()[0], 'Kirk', 'get first complex') @@ -950,7 +935,7 @@ def lists_remove_random_item(myList): # Tests the "get and remove" block. def test_getRemove(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults list2 = ['Kirk', 'Spock', 'McCoy'] assertEquals(list2.pop(0), 'Kirk', 'getremove first') assertEquals(list2, ['Spock', 'McCoy'], 'getremove first list') @@ -985,7 +970,7 @@ def test_getRemove(): # Tests the "remove" block. def test_remove(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults list2 = ['Kirk', 'Spock', 'McCoy'] list2.pop(0) assertEquals(list2, ['Spock', 'McCoy'], 'remove first list') @@ -1021,7 +1006,7 @@ def test_remove(): # Tests the "set" block. def test_set(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults list2 = ['Picard', 'Riker', 'Crusher'] list2[0] = 'Jean-Luc' assertEquals(list2, ['Jean-Luc', 'Riker', 'Crusher'], 'set first list') @@ -1060,7 +1045,7 @@ def test_set(): # Tests the "insert" block. def test_insert(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults list2 = ['Picard', 'Riker', 'Crusher'] list2.insert(0, 'Data') assertEquals(list2, ['Data', 'Picard', 'Riker', 'Crusher'], 'insert first list') @@ -1099,7 +1084,7 @@ def test_insert(): # Tests the "get sub-list" block with a variable. def test_sublist_simple(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults list2 = ['Columbia', 'Challenger', 'Discovery', 'Atlantis', 'Endeavour'] assertEquals(list2[1 : 3], ['Challenger', 'Discovery'], 'sublist # simple') assertEquals(list2[int((2 if True else None) - 1) : int(3 if True else None)], ['Challenger', 'Discovery'], 'sublist # simple order') @@ -1124,13 +1109,13 @@ def test_sublist_simple(): # Creates a list for use with the sublist test. def get_space_shuttles(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults number_of_calls = (number_of_calls if isinstance(number_of_calls, Number) else 0) + 1 return ['Columbia', 'Challenger', 'Discovery', 'Atlantis', 'Endeavour'] # Tests the "get sub-list" block with a function call. def test_sublist_complex(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults number_of_calls = 0 assertEquals(get_space_shuttles()[1 : 3], ['Challenger', 'Discovery'], 'sublist # start complex') check_number_of_calls('sublist # start complex') @@ -1178,14 +1163,14 @@ def test_sublist_complex(): # Tests the "join" block. def test_join(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults list2 = ['Vulcan', 'Klingon', 'Borg'] assertEquals(','.join(list2), 'Vulcan,Klingon,Borg', 'join') assertEquals(','.join(list2 if True else None), 'Vulcan,Klingon,Borg', 'join order') # Tests the "split" block. def test_split(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults text = 'Vulcan,Klingon,Borg' assertEquals(text.split(','), ['Vulcan', 'Klingon', 'Borg'], 'split') assertEquals((text if True else None).split(','), ['Vulcan', 'Klingon', 'Borg'], 'split order') @@ -1207,78 +1192,37 @@ def lists_sort(my_list, type, reverse): # Tests the "alphabetic sort" block. def test_sort_alphabetic(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults list2 = ['Vulcan', 'klingon', 'Borg'] assertEquals(lists_sort(list2, "TEXT", False), ['Borg', 'Vulcan', 'klingon'], 'sort alphabetic ascending') assertEquals(lists_sort(list2 if True else None, "TEXT", False), ['Borg', 'Vulcan', 'klingon'], 'sort alphabetic ascending order') # Tests the "alphabetic sort ignore case" block. def test_sort_ignoreCase(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults list2 = ['Vulcan', 'klingon', 'Borg'] assertEquals(lists_sort(list2, "IGNORE_CASE", False), ['Borg', 'klingon', 'Vulcan'], 'sort ignore case ascending') assertEquals(lists_sort(list2 if True else None, "IGNORE_CASE", False), ['Borg', 'klingon', 'Vulcan'], 'sort ignore case ascending order') # Tests the "numeric sort" block. def test_sort_numeric(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults list2 = [8, 18, -1] assertEquals(lists_sort(list2, "NUMERIC", True), [18, 8, -1], 'sort numeric descending') assertEquals(lists_sort(list2 if True else None, "NUMERIC", True), [18, 8, -1], 'sort numeric descending order') # Tests the "list reverse" block. def test_lists_reverse(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults list2 = [8, 18, -1, 64] assertEquals(list(reversed(list2)), [64, -1, 18, 8], 'reverse a copy') assertEquals(list2, [8, 18, -1, 64], 'reverse a copy original') list2 = [] assertEquals(list(reversed(list2)), [], 'empty list') -# Describe this function... -def test_colour_picker(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults - assertEquals('#ff6600', '#ff6600', 'static colour') - -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) - -# Describe this function... -def test_rgb(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults - assertEquals(colour_rgb(100, 40, 0), '#ff6600', 'from rgb') - -# Describe this function... -def test_colour_random(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults - for count4 in range(100): - item = '#%06x' % random.randint(0, 2**24 - 1) - assertEquals(len(item), 7, 'length of random colour string: ' + str(item)) - assertEquals(item[0], '#', 'format of random colour string: ' + str(item)) - for i in range(1, 7): - assertEquals(0 != 'abcdefABDEF0123456789'.find(item[int((i + 1) - 1)]) + 1, True, ''.join([str(x4) for x4 in ['contents of random colour string: ', item, ' at index: ', i + 1]])) - -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) - -# Describe this function... -def test_blend(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults - assertEquals(colour_blend('#ff0000', colour_rgb(100, 40, 0), 0.4), '#ff2900', 'blend') - # Describe this function... def test_procedure(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults procedure_1(8, 2) assertEquals(proc_z, 4, 'procedure with global') proc_w = False @@ -1290,19 +1234,19 @@ def test_procedure(): # Describe this function... def procedure_1(proc_x, proc_y): - global test_name, naked, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults proc_z = proc_x / proc_y # Describe this function... def procedure_2(proc_x): - global test_name, naked, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults if proc_x: return proc_w = True # Describe this function... def test_function(): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults assertEquals(function_1(2, 3), -1, 'function with arguments') assertEquals(func_z, 'side effect', 'function with side effect') func_a = 'unchanged' @@ -1314,28 +1258,28 @@ def test_function(): # Describe this function... def function_1(func_x, func_y): - global test_name, naked, proc_x, proc_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_a, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults func_z = 'side effect' return func_x - func_y # Describe this function... def function_2(func_a): - global test_name, naked, proc_x, proc_y, func_x, func_y, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults func_a = (func_a if isinstance(func_a, Number) else 0) + 1 return str(func_a) + str(func_c) # Describe this function... def function_3(func_a): - global test_name, naked, proc_x, proc_y, func_x, func_y, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, n, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults if func_a: return True return False # Describe this function... def recurse(n): - global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, i, loglist, changing_list, list_copy, unittestResults + global test_name, naked, proc_x, proc_y, func_x, func_y, func_a, ok, log, count, varToChange, rand, item, text, number_of_calls, list2, proc_z, func_z, x, proc_w, func_c, if2, loglist, changing_list, list_copy, unittestResults if n > 0: - text = ''.join([str(x5) for x5 in [recurse(n - 1), n, recurse(n - 1)]]) + text = ''.join([str(x4) for x4 in [recurse(n - 1), n, recurse(n - 1)]]) else: text = '-' return text @@ -1414,7 +1358,6 @@ test_trim() test_count_text() test_text_reverse() test_replace() -test_multiline() print(unittest_report()) unittestResults = None @@ -1443,15 +1386,6 @@ test_lists_reverse() print(unittest_report()) unittestResults = None -unittestResults = [] -print('\n====================\n\nRunning suite: Colour') -test_colour_picker() -test_blend() -test_rgb() -test_colour_random() -print(unittest_report()) -unittestResults = None - unittestResults = [] print('\n====================\n\nRunning suite: Variables') item = 123 diff --git a/tests/generators/index.html b/tests/generators/index.html index cb30c0966..9ec1db773 100644 --- a/tests/generators/index.html +++ b/tests/generators/index.html @@ -321,7 +321,6 @@ h1 { - @@ -337,12 +336,6 @@ h1 { - - - - - - @@ -364,7 +357,6 @@ h1 { Math
Text
Lists
- Colour
Variables
Functions
diff --git a/tests/generators/text.xml b/tests/generators/text.xml index d10593735..61e961aef 100644 --- a/tests/generators/text.xml +++ b/tests/generators/text.xml @@ -46,11 +46,6 @@ - - - - - @@ -4653,93 +4648,4 @@ - - test multiline - Tests the "multiline" block. - - - - - no text - - - - - - - - - - - - - - - - - simple - - - - - Google - - - - - Google - - - - - - - no compile error with newlines - - - - - paragraph with newlines yup - - - - - paragraph with newlines yup - - - - - - - count with newlines - - - - - - - bark - - - - - bark bark bark bark bark bark bark bark bark - - - - - - - 9 - - - - - - - - - - - \ No newline at end of file diff --git a/tests/mocha/field_angle_test.js b/tests/mocha/field_angle_test.js deleted file mode 100644 index 62afd8d27..000000000 --- a/tests/mocha/field_angle_test.js +++ /dev/null @@ -1,389 +0,0 @@ -/** - * @license - * Copyright 2019 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as Blockly from '../../build/src/core/blockly.js'; -import { - assertFieldValue, - runConstructorSuiteTests, - runFromJsonSuiteTests, - runSetValueTests, -} from './test_helpers/fields.js'; -import { - createTestBlock, - defineRowBlock, -} from './test_helpers/block_definitions.js'; -import { - sharedTestSetup, - sharedTestTeardown, - workspaceTeardown, -} from './test_helpers/setup_teardown.js'; - -suite('Angle Fields', function () { - setup(function () { - sharedTestSetup.call(this); - }); - teardown(function () { - sharedTestTeardown.call(this); - }); - /** - * Configuration for field tests with invalid values. - * @type {!Array} - */ - const invalidValueTestCases = [ - {title: 'Undefined', value: undefined}, - {title: 'Null', value: null}, - {title: 'NaN', value: NaN}, - {title: 'Non-Parsable String', value: 'bad'}, - {title: 'Infinity', value: Infinity, expectedValue: Infinity}, - {title: 'Negative Infinity', value: -Infinity, expectedValue: -Infinity}, - {title: 'Infinity String', value: 'Infinity', expectedValue: Infinity}, - { - title: 'Negative Infinity String', - value: '-Infinity', - expectedValue: -Infinity, - }, - ]; - /** - * Configuration for field tests with valid values. - * @type {!Array} - */ - - const validValueTestCases = [ - {title: 'Integer', value: 1, expectedValue: 1}, - {title: 'Float', value: 1.5, expectedValue: 1.5}, - {title: 'Integer String', value: '1', expectedValue: 1}, - {title: 'Float String', value: '1.5', expectedValue: 1.5}, - {title: '> 360°', value: 362, expectedValue: 2}, - ]; - const addArgsAndJson = function (testCase) { - testCase.args = [testCase.value]; - testCase.json = {'angle': testCase.value}; - }; - invalidValueTestCases.forEach(addArgsAndJson); - validValueTestCases.forEach(addArgsAndJson); - - /** - * The expected default value for the field being tested. - * @type {*} - */ - const defaultFieldValue = 0; - /** - * Asserts that the field property values are set to default. - * @param {FieldTemplate} field The field to check. - */ - const assertFieldDefault = function (field) { - assertFieldValue(field, defaultFieldValue); - }; - /** - * Asserts that the field properties are correct based on the test case. - * @param {!Blockly.FieldAngle} field The field to check. - * @param {!FieldValueTestCase} testCase The test case. - */ - const validTestCaseAssertField = function (field, testCase) { - assertFieldValue(field, testCase.expectedValue); - }; - - runConstructorSuiteTests( - Blockly.FieldAngle, - validValueTestCases, - invalidValueTestCases, - validTestCaseAssertField, - assertFieldDefault, - ); - - runFromJsonSuiteTests( - Blockly.FieldAngle, - validValueTestCases, - invalidValueTestCases, - validTestCaseAssertField, - assertFieldDefault, - ); - - suite('setValue', function () { - suite('Empty -> New Value', function () { - setup(function () { - this.field = new Blockly.FieldAngle(); - }); - runSetValueTests( - validValueTestCases, - invalidValueTestCases, - defaultFieldValue, - ); - test('With source block', function () { - this.field.setSourceBlock(createTestBlock()); - this.field.setValue(2.5); - assertFieldValue(this.field, 2.5); - }); - }); - suite('Value -> New Value', function () { - const initialValue = 1; - setup(function () { - this.field = new Blockly.FieldAngle(initialValue); - }); - runSetValueTests( - validValueTestCases, - invalidValueTestCases, - initialValue, - ); - test('With source block', function () { - this.field.setSourceBlock(createTestBlock()); - this.field.setValue(2.5); - assertFieldValue(this.field, 2.5); - }); - }); - }); - suite('Validators', function () { - setup(function () { - this.field = new Blockly.FieldAngle(1); - this.field.htmlInput_ = document.createElement('input'); - this.field.htmlInput_.setAttribute('data-old-value', '1'); - this.field.htmlInput_.setAttribute('data-untyped-default-value', '1'); - this.stub = sinon.stub(this.field, 'resizeEditor_'); - }); - teardown(function () { - sinon.restore(); - }); - const testSuites = [ - { - title: 'Null Validator', - validator: function () { - return null; - }, - value: 2, - expectedValue: '1', - }, - { - title: 'Force Mult of 30 Validator', - validator: function (newValue) { - return Math.round(newValue / 30) * 30; - }, - value: 25, - expectedValue: 30, - }, - { - title: 'Returns Undefined Validator', - validator: function () {}, - value: 2, - expectedValue: 2, - }, - ]; - testSuites.forEach(function (suiteInfo) { - suite(suiteInfo.title, function () { - setup(function () { - this.field.setValidator(suiteInfo.validator); - }); - test('When Editing', function () { - this.field.isBeingEdited_ = true; - this.field.htmlInput_.value = String(suiteInfo.value); - this.field.onHtmlInputChange_(null); - assertFieldValue( - this.field, - suiteInfo.expectedValue, - String(suiteInfo.value), - ); - }); - test('When Not Editing', function () { - this.field.setValue(suiteInfo.value); - assertFieldValue(this.field, +suiteInfo.expectedValue); - }); - }); - }); - }); - suite('Customizations', function () { - suite('Clockwise', function () { - test('JS Configuration', function () { - const field = new Blockly.FieldAngle(0, null, { - clockwise: true, - }); - chai.assert.isTrue(field.clockwise); - }); - test('JSON Definition', function () { - const field = Blockly.FieldAngle.fromJson({ - value: 0, - clockwise: true, - }); - chai.assert.isTrue(field.clockwise); - }); - test('Constant', function () { - // Note: Generally constants should be set at compile time, not - // runtime (since they are constants) but for testing purposes we - // can do this. - Blockly.FieldAngle.CLOCKWISE = true; - const field = new Blockly.FieldAngle(); - chai.assert.isTrue(field.clockwise); - }); - }); - suite('Offset', function () { - test('JS Configuration', function () { - const field = new Blockly.FieldAngle(0, null, { - offset: 90, - }); - chai.assert.equal(field.offset, 90); - }); - test('JSON Definition', function () { - const field = Blockly.FieldAngle.fromJson({ - value: 0, - offset: 90, - }); - chai.assert.equal(field.offset, 90); - }); - test('Constant', function () { - // Note: Generally constants should be set at compile time, not - // runtime (since they are constants) but for testing purposes we - // can do this. - Blockly.FieldAngle.OFFSET = 90; - const field = new Blockly.FieldAngle(); - chai.assert.equal(field.offset, 90); - }); - test('Null', function () { - // Note: Generally constants should be set at compile time, not - // runtime (since they are constants) but for testing purposes we - // can do this. - Blockly.FieldAngle.OFFSET = 90; - const field = Blockly.FieldAngle.fromJson({ - value: 0, - offset: null, - }); - chai.assert.equal(field.offset, 90); - }); - }); - suite('Wrap', function () { - test('JS Configuration', function () { - const field = new Blockly.FieldAngle(0, null, { - wrap: 180, - }); - chai.assert.equal(field.wrap, 180); - }); - test('JSON Definition', function () { - const field = Blockly.FieldAngle.fromJson({ - value: 0, - wrap: 180, - }); - chai.assert.equal(field.wrap, 180); - }); - test('Constant', function () { - // Note: Generally constants should be set at compile time, not - // runtime (since they are constants) but for testing purposes we - // can do this. - Blockly.FieldAngle.WRAP = 180; - const field = new Blockly.FieldAngle(); - chai.assert.equal(field.wrap, 180); - }); - test('Null', function () { - // Note: Generally constants should be set at compile time, not - // runtime (since they are constants) but for testing purposes we - // can do this. - Blockly.FieldAngle.WRAP = 180; - const field = Blockly.FieldAngle.fromJson({ - value: 0, - wrap: null, - }); - chai.assert.equal(field.wrap, 180); - }); - }); - suite('Round', function () { - test('JS Configuration', function () { - const field = new Blockly.FieldAngle(0, null, { - round: 30, - }); - chai.assert.equal(field.round, 30); - }); - test('JSON Definition', function () { - const field = Blockly.FieldAngle.fromJson({ - value: 0, - round: 30, - }); - chai.assert.equal(field.round, 30); - }); - test('Constant', function () { - // Note: Generally constants should be set at compile time, not - // runtime (since they are constants) but for testing purposes we - // can do this. - Blockly.FieldAngle.ROUND = 30; - const field = new Blockly.FieldAngle(); - chai.assert.equal(field.round, 30); - }); - test('Null', function () { - // Note: Generally constants should be set at compile time, not - // runtime (since they are constants) but for testing purposes we - // can do this. - Blockly.FieldAngle.ROUND = 30; - const field = Blockly.FieldAngle.fromJson({ - value: 0, - round: null, - }); - chai.assert.equal(field.round, 30); - }); - }); - suite('Mode', function () { - suite('Compass', function () { - test('JS Configuration', function () { - const field = new Blockly.FieldAngle(0, null, { - mode: 'compass', - }); - chai.assert.equal(field.offset, 90); - chai.assert.isTrue(field.clockwise); - }); - test('JS Configuration', function () { - const field = Blockly.FieldAngle.fromJson({ - value: 0, - mode: 'compass', - }); - chai.assert.equal(field.offset, 90); - chai.assert.isTrue(field.clockwise); - }); - }); - suite('Protractor', function () { - test('JS Configuration', function () { - const field = new Blockly.FieldAngle(0, null, { - mode: 'protractor', - }); - chai.assert.equal(field.offset, 0); - chai.assert.isFalse(field.clockwise); - }); - test('JS Configuration', function () { - const field = Blockly.FieldAngle.fromJson({ - value: 0, - mode: 'protractor', - }); - chai.assert.equal(field.offset, 0); - chai.assert.isFalse(field.clockwise); - }); - }); - }); - }); - - suite('Serialization', function () { - setup(function () { - this.workspace = new Blockly.Workspace(); - defineRowBlock(); - - this.assertValue = (value) => { - const block = this.workspace.newBlock('row_block'); - const field = new Blockly.FieldAngle(value); - block.getInput('INPUT').appendField(field, 'ANGLE'); - const jso = Blockly.serialization.blocks.save(block); - chai.assert.deepEqual(jso['fields'], {'ANGLE': value}); - }; - }); - - teardown(function () { - workspaceTeardown.call(this, this.workspace); - }); - - test('Simple', function () { - this.assertValue(90); - }); - - test('Max precision', function () { - this.assertValue(1.000000000000001); - }); - - test('Smallest number', function () { - this.assertValue(5e-324); - }); - }); -}); diff --git a/tests/mocha/field_multilineinput_test.js b/tests/mocha/field_multilineinput_test.js deleted file mode 100644 index abdf68237..000000000 --- a/tests/mocha/field_multilineinput_test.js +++ /dev/null @@ -1,284 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as Blockly from '../../build/src/core/blockly.js'; -import { - assertFieldValue, - runConstructorSuiteTests, - runFromJsonSuiteTests, - runSetValueTests, -} from './test_helpers/fields.js'; -import { - createTestBlock, - defineRowBlock, -} from './test_helpers/block_definitions.js'; -import { - sharedTestSetup, - sharedTestTeardown, - workspaceTeardown, -} from './test_helpers/setup_teardown.js'; -import {runCodeGenerationTestSuites} from './test_helpers/code_generation.js'; -import {dartGenerator} from '../../build/src/generators/dart.js'; -import {javascriptGenerator} from '../../build/src/generators/javascript.js'; -import {luaGenerator} from '../../build/src/generators/lua.js'; -import {phpGenerator} from '../../build/src/generators/php.js'; -import {pythonGenerator} from '../../build/src/generators/python.js'; - -suite('Multiline Input Fields', function () { - setup(function () { - sharedTestSetup.call(this); - }); - teardown(function () { - sharedTestTeardown.call(this); - }); - /** - * Configuration for field tests with invalid values. - * @type {!Array} - */ - const invalidValueTestCases = [ - {title: 'Undefined', value: undefined}, - {title: 'Null', value: null}, - ]; - /** - * Configuration for field tests with valid values. - * @type {!Array} - */ - const validValueTestCases = [ - {title: 'Empty string', value: '', expectedValue: ''}, - {title: 'String no newline', value: 'value', expectedValue: 'value'}, - { - title: 'String with newline', - value: 'bark bark\n bark bark bark\n bark bar bark bark\n', - expectedValue: 'bark bark\n bark bark bark\n bark bar bark bark\n', - }, - {title: 'Boolean true', value: true, expectedValue: 'true'}, - {title: 'Boolean false', value: false, expectedValue: 'false'}, - {title: 'Number (Truthy)', value: 1, expectedValue: '1'}, - {title: 'Number (Falsy)', value: 0, expectedValue: '0'}, - {title: 'NaN', value: NaN, expectedValue: 'NaN'}, - ]; - const addArgsAndJson = function (testCase) { - testCase.args = [testCase.value]; - testCase.json = {'text': testCase.value}; - }; - invalidValueTestCases.forEach(addArgsAndJson); - validValueTestCases.forEach(addArgsAndJson); - - /** - * The expected default value for the field being tested. - * @type {*} - */ - const defaultFieldValue = ''; - /** - * Asserts that the field property values are set to default. - * @param {!Blockly.FieldMultilineInput} field The field to check. - */ - const assertFieldDefault = function (field) { - assertFieldValue(field, defaultFieldValue); - }; - /** - * Asserts that the field properties are correct based on the test case. - * @param {!Blockly.FieldMultilineInput} field The field to check. - * @param {!FieldValueTestCase} testCase The test case. - */ - const validTestCaseAssertField = function (field, testCase) { - assertFieldValue(field, testCase.expectedValue); - }; - - runConstructorSuiteTests( - Blockly.FieldMultilineInput, - validValueTestCases, - invalidValueTestCases, - validTestCaseAssertField, - assertFieldDefault, - ); - - runFromJsonSuiteTests( - Blockly.FieldMultilineInput, - validValueTestCases, - invalidValueTestCases, - validTestCaseAssertField, - assertFieldDefault, - ); - - suite('setValue', function () { - suite('Empty -> New Value', function () { - setup(function () { - this.field = new Blockly.FieldMultilineInput(); - }); - runSetValueTests( - validValueTestCases, - invalidValueTestCases, - defaultFieldValue, - ); - test('With source block', function () { - this.field.setSourceBlock(createTestBlock()); - this.field.setValue('value'); - assertFieldValue(this.field, 'value'); - }); - }); - suite('Value -> New Value', function () { - const initialValue = 'oldValue'; - setup(function () { - this.field = new Blockly.FieldMultilineInput(initialValue); - }); - runSetValueTests( - validValueTestCases, - invalidValueTestCases, - initialValue, - ); - test('With source block', function () { - this.field.setSourceBlock(createTestBlock()); - this.field.setValue('value'); - assertFieldValue(this.field, 'value'); - }); - }); - }); - - suite('blockToCode', function () { - setup(function () { - this.workspace = new Blockly.Workspace(); - }); - const createBlockFn = (value) => { - return (workspace) => { - const block = workspace.newBlock('text_multiline'); - const textField = block.getField('TEXT'); - textField.setValue(value); - return block; - }; - }; - - /** - * Test suites for code generation tests.s - * @type {Array} - */ - const testSuites = [ - { - title: 'Dart', - generator: dartGenerator, - testCases: [ - { - title: 'Empty string', - expectedCode: "''", - createBlock: createBlockFn(''), - }, - { - title: 'String with newline', - expectedCode: - "'bark bark' + '\\n' + \n' bark bark bark' + '\\n' + \n' bark bar bark bark' + '\\n' + \n''", - createBlock: createBlockFn( - 'bark bark\n bark bark bark\n bark bar bark bark\n', - ), - }, - ], - }, - { - title: 'JavaScript', - generator: javascriptGenerator, - testCases: [ - { - title: 'Empty string', - expectedCode: "''", - createBlock: createBlockFn(''), - }, - { - title: 'String with newline', - expectedCode: - "'bark bark' + '\\n' +\n' bark bark bark' + '\\n' +\n' bark bar bark bark' + '\\n' +\n''", - createBlock: createBlockFn( - 'bark bark\n bark bark bark\n bark bar bark bark\n', - ), - }, - ], - }, - { - title: 'Lua', - generator: luaGenerator, - testCases: [ - { - title: 'Empty string', - expectedCode: "''", - createBlock: createBlockFn(''), - }, - { - title: 'String with newline', - expectedCode: - "'bark bark' .. '\\n' ..\n' bark bark bark' .. '\\n' ..\n' bark bar bark bark' .. '\\n' ..\n''", - createBlock: createBlockFn( - 'bark bark\n bark bark bark\n bark bar bark bark\n', - ), - }, - ], - }, - { - title: 'PHP', - generator: phpGenerator, - testCases: [ - { - title: 'Empty string', - expectedCode: "''", - createBlock: createBlockFn(''), - }, - { - title: 'String with newline', - expectedCode: - "'bark bark' . \"\\n\" .\n' bark bark bark' . \"\\n\" .\n' bark bar bark bark' . \"\\n\" .\n''", - createBlock: createBlockFn( - 'bark bark\n bark bark bark\n bark bar bark bark\n', - ), - }, - ], - }, - { - title: 'Python', - generator: pythonGenerator, - testCases: [ - { - title: 'Empty string', - expectedCode: "''", - createBlock: createBlockFn(''), - }, - { - title: 'String with newline', - expectedCode: - "'bark bark' + '\\n' + \n' bark bark bark' + '\\n' + \n' bark bar bark bark' + '\\n' + \n''", - createBlock: createBlockFn( - 'bark bark\n bark bark bark\n bark bar bark bark\n', - ), - }, - ], - }, - ]; - runCodeGenerationTestSuites(testSuites); - }); - - suite('Serialization', function () { - setup(function () { - this.workspace = new Blockly.Workspace(); - defineRowBlock(); - - this.assertValue = (value) => { - const block = this.workspace.newBlock('row_block'); - const field = new Blockly.FieldMultilineInput(value); - block.getInput('INPUT').appendField(field, 'MULTILINE'); - const jso = Blockly.serialization.blocks.save(block); - chai.assert.deepEqual(jso['fields'], {'MULTILINE': value}); - }; - }); - - teardown(function () { - workspaceTeardown.call(this, this.workspace); - }); - - test('Single line', function () { - this.assertValue('this is a single line'); - }); - - test('Multiple lines', function () { - this.assertValue('this\nis\n multiple\n lines'); - }); - }); -}); diff --git a/tests/mocha/index.html b/tests/mocha/index.html index 7cebc68ed..d08d24ad8 100644 --- a/tests/mocha/index.html +++ b/tests/mocha/index.html @@ -76,14 +76,11 @@ import './event_var_rename_test.js'; import './event_viewport_test.js'; import './extensions_test.js'; - import './field_angle_test.js'; import './field_checkbox_test.js'; - import './field_colour_test.js'; import './field_dropdown_test.js'; import './field_image_test.js'; import './field_label_serializable_test.js'; import './field_label_test.js'; - import './field_multilineinput_test.js'; import './field_number_test.js'; import './field_registry_test.js'; import './field_test.js'; diff --git a/tests/mocha/serializer_test.js b/tests/mocha/serializer_test.js index bd9a47344..500c10684 100644 --- a/tests/mocha/serializer_test.js +++ b/tests/mocha/serializer_test.js @@ -223,55 +223,6 @@ Serializer.Attributes.testSuites = [ Serializer.Fields = new SerializerTestSuite('Fields'); -Serializer.Fields.Angle = new SerializerTestSuite('Angle'); -Serializer.Fields.Angle.Simple = new SerializerTestCase( - 'Simple', - '' + - '' + - '90' + - '' + - '', -); -Serializer.Fields.Angle.Negative = new SerializerTestCase( - 'Negative', - '' + - '' + - '-90' + - '' + - '', -); -Serializer.Fields.Angle.Decimals = new SerializerTestCase( - 'Decimals', - '' + - '' + - '1.5' + - '' + - '', -); -Serializer.Fields.Angle.MaxPrecision = new SerializerTestCase( - 'MaxPrecision', - '' + - '' + - '1.000000000000001' + - '' + - '', -); -Serializer.Fields.Angle.SmallestNumber = new SerializerTestCase( - 'SmallestNumber', - '' + - '' + - '5e-324' + - '' + - '', -); -Serializer.Fields.Angle.testCases = [ - Serializer.Fields.Angle.Simple, - Serializer.Fields.Angle.Negative, - Serializer.Fields.Angle.Decimals, - Serializer.Fields.Angle.MaxPrecision, - Serializer.Fields.Angle.SmallestNumber, -]; - Serializer.Fields.Checkbox = new SerializerTestSuite('Checkbox'); Serializer.Fields.Checkbox.True = new SerializerTestCase( 'True', @@ -294,37 +245,6 @@ Serializer.Fields.Checkbox.testCases = [ Serializer.Fields.Checkbox.False, ]; -Serializer.Fields.Colour = new SerializerTestSuite('Colour'); -Serializer.Fields.Colour.ThreeChar = new SerializerTestCase( - 'ThreeChar', - '' + - '' + - '#ffcc00' + // Could use a 3 char code. - '' + - '', -); -Serializer.Fields.Colour.SixChar = new SerializerTestCase( - 'SixChar', - '' + - '' + - '#f1c101' + - '' + - '', -); -Serializer.Fields.Colour.Black = new SerializerTestCase( - 'Black', - '' + - '' + - '#000000' + - '' + - '', -); -Serializer.Fields.Colour.testCases = [ - Serializer.Fields.Colour.ThreeChar, - Serializer.Fields.Colour.SixChar, - Serializer.Fields.Colour.Black, -]; - Serializer.Fields.Dropdown = new SerializerTestSuite('Dropdown'); Serializer.Fields.Dropdown.Default = new SerializerTestCase( 'Default', @@ -462,141 +382,6 @@ Serializer.Fields.LabelSerializable.testCases = [ // Serializer.Fields.LabelSerializable.ControlChars, ]; -Serializer.Fields.MultilineInput = new SerializerTestSuite('MultilineInput'); -Serializer.Fields.MultilineInput.SingleLine = new SerializerTestCase( - 'SingleLine', - '' + - '' + - 'test' + - '' + - '', -); -Serializer.Fields.MultilineInput.MultipleLines = new SerializerTestCase( - 'MultipleLines', - '' + - '' + - 'line1&#10;line2&#10;line3' + - '' + - '', -); -Serializer.Fields.MultilineInput.Indentation = new SerializerTestCase( - 'Indentation', - '' + - '' + - 'line1&#10; line2&#10; line3' + - '' + - '', -); -/* eslint-disable no-tabs */ -Serializer.Fields.MultilineInput.Tabs = new SerializerTestCase( - 'Tabs', - '' + - '' + - '' + - 'line1&#10;&#x9line2&#10;&#x9line3' + - '' + - '' + - '', -); -/* eslint-enable no-tabs */ -Serializer.Fields.MultilineInput.Symbols = new SerializerTestCase( - 'Symbols', - '' + - '' + - '~`!@#$%^*()_+-={[}]|\\:;,.?/' + - '' + - '', -); -Serializer.Fields.MultilineInput.EscapedSymbols = new SerializerTestCase( - 'EscapedSymbols', - '' + - '' + - '&<>' + - '' + - '', -); -Serializer.Fields.MultilineInput.SingleQuotes = new SerializerTestCase( - 'SingleQuotes', - '' + - '' + - '\'test\'' + - '' + - '', -); -Serializer.Fields.MultilineInput.DoubleQuotes = new SerializerTestCase( - 'DoubleQuotes', - '' + - '' + - '"test"' + - '' + - '', -); -Serializer.Fields.MultilineInput.Numbers = new SerializerTestCase( - 'Numbers', - '' + - '' + - '1234567890a123a123a' + - '' + - '', -); -Serializer.Fields.MultilineInput.Emoji = new SerializerTestCase( - 'Emoji', - '' + - '' + - '😀👋🏿👋🏾👋🏽👋🏼👋🏻😀❤❤❤' + - '' + - '', -); -Serializer.Fields.MultilineInput.Russian = new SerializerTestCase( - 'Russian', - '' + - '' + - 'ты любопытный кот' + - '' + - '', -); -Serializer.Fields.MultilineInput.Japanese = new SerializerTestCase( - 'Japanese', - '' + - '' + - 'あなたは好奇心旺盛な猫です' + - '' + - '', -); -Serializer.Fields.MultilineInput.Zalgo = new SerializerTestCase( - 'Zalgo', - '' + - '' + - 'z̴̪͈̲̜͕̽̈̀͒͂̓̋̉̍a̸̧̧̜̻̘̤̫̱̲͎̞̻͆̋ļ̸̛̖̜̳͚̖͔̟̈́͂̉̀͑̑͑̎ǵ̸̫̳̽̐̃̑̚̕o̶͇̫͔̮̼̭͕̹̘̬͋̀͆̂̇̋͊̒̽' + - '' + - '', -); -Serializer.Fields.MultilineInput.ControlChars = new SerializerTestCase( - 'ControlChars', - '' + - '' + - '&#a1;' + - '' + - '', -); -Serializer.Fields.MultilineInput.testCases = [ - Serializer.Fields.MultilineInput.SingleLine, - Serializer.Fields.MultilineInput.MultipleLines, - Serializer.Fields.MultilineInput.Indentation, - Serializer.Fields.MultilineInput.Tabs, - Serializer.Fields.MultilineInput.Symbols, - Serializer.Fields.MultilineInput.EscapedSymbols, - Serializer.Fields.MultilineInput.SingleQuotes, - Serializer.Fields.MultilineInput.DoubleQuotes, - Serializer.Fields.MultilineInput.Numbers, - Serializer.Fields.MultilineInput.Emoji, - Serializer.Fields.MultilineInput.Russian, - Serializer.Fields.MultilineInput.Japanese, - Serializer.Fields.MultilineInput.Zalgo, - // TODO: Uncoment once #4945 is merged. - // Serializer.Fields.MultilineInput.ControlChars, -]; - Serializer.Fields.Number = new SerializerTestSuite('Number'); Serializer.Fields.Number.Simple = new SerializerTestCase( 'Simple', @@ -1070,12 +855,9 @@ Serializer.Fields.Variable.Id.testSuites = [ Serializer.Fields.Variable.testSuites = [Serializer.Fields.Variable.Id]; Serializer.Fields.testSuites = [ - Serializer.Fields.Angle, Serializer.Fields.Checkbox, - Serializer.Fields.Colour, Serializer.Fields.Dropdown, Serializer.Fields.LabelSerializable, - Serializer.Fields.MultilineInput, Serializer.Fields.Number, Serializer.Fields.TextInput, Serializer.Fields.Variable, diff --git a/tests/mocha/xml_test.js b/tests/mocha/xml_test.js index 385b0f736..7bd16afd0 100644 --- a/tests/mocha/xml_test.js +++ b/tests/mocha/xml_test.js @@ -127,27 +127,6 @@ suite('XML', function () { workspaceTeardown.call(this, this.workspace); }); suite('Fields', function () { - test('Angle', function () { - Blockly.defineBlocksWithJsonArray([ - { - 'type': 'field_angle_test_block', - 'message0': '%1', - 'args0': [ - { - 'type': 'field_angle', - 'name': 'ANGLE', - 'angle': 90, - }, - ], - }, - ]); - const block = new Blockly.Block( - this.workspace, - 'field_angle_test_block', - ); - const resultFieldDom = Blockly.Xml.blockToDom(block).childNodes[0]; - assertNonVariableField(resultFieldDom, 'ANGLE', '90'); - }); test('Checkbox', function () { Blockly.defineBlocksWithJsonArray([ { @@ -169,27 +148,6 @@ suite('XML', function () { const resultFieldDom = Blockly.Xml.blockToDom(block).childNodes[0]; assertNonVariableField(resultFieldDom, 'CHECKBOX', 'TRUE'); }); - test('Colour', function () { - Blockly.defineBlocksWithJsonArray([ - { - 'type': 'field_colour_test_block', - 'message0': '%1', - 'args0': [ - { - 'type': 'field_colour', - 'name': 'COLOUR', - 'colour': '#000099', - }, - ], - }, - ]); - const block = new Blockly.Block( - this.workspace, - 'field_colour_test_block', - ); - const resultFieldDom = Blockly.Xml.blockToDom(block).childNodes[0]; - assertNonVariableField(resultFieldDom, 'COLOUR', '#000099'); - }); test('Dropdown', function () { Blockly.defineBlocksWithJsonArray([ { diff --git a/tests/multi_playground.html b/tests/multi_playground.html index f929de8d8..ff59127e6 100644 --- a/tests/multi_playground.html +++ b/tests/multi_playground.html @@ -342,7 +342,6 @@ - @@ -469,44 +468,6 @@ - - - - - - - 100 - - - - - 50 - - - - - 0 - - - - - - - #ff0000 - - - - - #3333ff - - - - - 0.5 - - - - - @@ -918,44 +917,6 @@ - - - - - - - 100 - - - - - 50 - - - - - 0 - - - - - - - #ff0000 - - - - - #3333ff - - - - - 0.5 - - - - - From e91dd203c393bb63c288a2ae0e2a474a537f9427 Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Thu, 14 Mar 2024 16:57:18 +0000 Subject: [PATCH 22/92] fix!: classes on text input bubble to match comment view (#7935) --- core/bubbles/bubble.ts | 12 ++++++-- core/bubbles/textinput_bubble.ts | 49 +++++++++----------------------- 2 files changed, 23 insertions(+), 38 deletions(-) diff --git a/core/bubbles/bubble.ts b/core/bubbles/bubble.ts index 4bea9d863..51b71d4ec 100644 --- a/core/bubbles/bubble.ts +++ b/core/bubbles/bubble.ts @@ -90,7 +90,11 @@ export abstract class Bubble implements IBubble { protected anchor: Coordinate, protected ownerRect?: Rect, ) { - this.svgRoot = dom.createSvgElement(Svg.G, {}, workspace.getBubbleCanvas()); + this.svgRoot = dom.createSvgElement( + Svg.G, + {'class': 'blocklyBubble'}, + workspace.getBubbleCanvas(), + ); const embossGroup = dom.createSvgElement( Svg.G, { @@ -100,7 +104,11 @@ export abstract class Bubble implements IBubble { }, this.svgRoot, ); - this.tail = dom.createSvgElement(Svg.PATH, {}, embossGroup); + this.tail = dom.createSvgElement( + Svg.PATH, + {'class': 'blocklyBubbleTail'}, + embossGroup, + ); this.background = dom.createSvgElement( Svg.RECT, { diff --git a/core/bubbles/textinput_bubble.ts b/core/bubbles/textinput_bubble.ts index 081f86097..7a8a44bdf 100644 --- a/core/bubbles/textinput_bubble.ts +++ b/core/bubbles/textinput_bubble.ts @@ -75,10 +75,11 @@ export class TextInputBubble extends Bubble { protected ownerRect?: Rect, ) { super(workspace, anchor, ownerRect); + dom.addClass(this.svgRoot, 'blocklyTextInputBubble'); ({inputRoot: this.inputRoot, textArea: this.textArea} = this.createEditor( this.contentContainer, )); - this.resizeGroup = this.createResizeHandle(this.svgRoot); + this.resizeGroup = this.createResizeHandle(this.svgRoot, workspace); this.setSize(this.DEFAULT_SIZE, true); } @@ -126,7 +127,7 @@ export class TextInputBubble extends Bubble { dom.HTML_NS, 'textarea', ) as HTMLTextAreaElement; - textArea.className = 'blocklyCommentTextarea'; + textArea.className = 'blocklyTextarea blocklyText'; textArea.setAttribute('dir', this.workspace.RTL ? 'RTL' : 'LTR'); body.appendChild(textArea); @@ -158,51 +159,27 @@ export class TextInputBubble extends Bubble { } /** Creates the resize handler elements and binds events to them. */ - private createResizeHandle(container: SVGGElement): SVGGElement { - const resizeGroup = dom.createSvgElement( - Svg.G, + private createResizeHandle( + container: SVGGElement, + workspace: WorkspaceSvg, + ): SVGGElement { + const resizeHandle = dom.createSvgElement( + Svg.IMAGE, { - 'class': this.workspace.RTL ? 'blocklyResizeSW' : 'blocklyResizeSE', + 'class': 'blocklyResizeHandle', + 'href': `${workspace.options.pathToMedia}resize-handle.svg`, }, container, ); - const size = 2 * Bubble.BORDER_WIDTH; - dom.createSvgElement( - Svg.POLYGON, - {'points': `0,${size} ${size},${size} ${size},0`}, - resizeGroup, - ); - dom.createSvgElement( - Svg.LINE, - { - 'class': 'blocklyResizeLine', - 'x1': size / 3, - 'y1': size - 1, - 'x2': size - 1, - 'y2': size / 3, - }, - resizeGroup, - ); - dom.createSvgElement( - Svg.LINE, - { - 'class': 'blocklyResizeLine', - 'x1': (size * 2) / 3, - 'y1': size - 1, - 'x2': size - 1, - 'y2': (size * 2) / 3, - }, - resizeGroup, - ); browserEvents.conditionalBind( - resizeGroup, + resizeHandle, 'pointerdown', this, this.onResizePointerDown, ); - return resizeGroup; + return resizeHandle; } /** From 8821c83cc9d694b7dcd900f73dd82fe6296030b0 Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Fri, 15 Mar 2024 18:20:08 +0000 Subject: [PATCH 23/92] feat: allow overriding comment icons (#7937) * feat: add comment icon interface * feat: have blocks construct comment icons from registry * chore: add tests for setCommentText * fix: typeguard --- core/block.ts | 31 ++++++++--- core/icons/icon_types.ts | 4 +- core/interfaces/i_comment_icon.ts | 33 ++++++++++++ tests/mocha/block_test.js | 88 +++++++++++++++++++++++++++++++ 4 files changed, 147 insertions(+), 9 deletions(-) create mode 100644 core/interfaces/i_comment_icon.ts diff --git a/core/block.ts b/core/block.ts index 394459e92..82c82cb43 100644 --- a/core/block.ts +++ b/core/block.ts @@ -34,8 +34,8 @@ import {Input} from './inputs/input.js'; import {Align} from './inputs/align.js'; import type {IASTNodeLocation} from './interfaces/i_ast_node_location.js'; import type {IDeletable} from './interfaces/i_deletable.js'; -import type {IIcon} from './interfaces/i_icon.js'; -import {CommentIcon} from './icons/comment_icon.js'; +import {type IIcon} from './interfaces/i_icon.js'; +import {isCommentIcon} from './interfaces/i_comment_icon.js'; import type {MutatorIcon} from './icons/mutator_icon.js'; import * as Tooltip from './tooltip.js'; import * as arrayUtils from './utils/array.js'; @@ -2212,7 +2212,7 @@ export class Block implements IASTNodeLocation, IDeletable { * @returns Block's comment. */ getCommentText(): string | null { - const comment = this.getIcon(CommentIcon.TYPE) as CommentIcon | null; + const comment = this.getIcon(IconType.COMMENT); return comment?.getText() ?? null; } @@ -2222,19 +2222,36 @@ export class Block implements IASTNodeLocation, IDeletable { * @param text The text, or null to delete. */ setCommentText(text: string | null) { - const comment = this.getIcon(CommentIcon.TYPE) as CommentIcon | null; + const comment = this.getIcon(IconType.COMMENT); const oldText = comment?.getText() ?? null; if (oldText === text) return; if (text !== null) { - let comment = this.getIcon(CommentIcon.TYPE) as CommentIcon | undefined; + let comment = this.getIcon(IconType.COMMENT); if (!comment) { - comment = this.addIcon(new CommentIcon(this)); + const commentConstructor = registry.getClass( + registry.Type.ICON, + IconType.COMMENT.toString(), + false, + ); + if (!commentConstructor) { + throw new Error( + 'No comment icon class is registered, so a comment cannot be set', + ); + } + const icon = new commentConstructor(this); + if (!isCommentIcon(icon)) { + throw new Error( + 'The class registered as a comment icon does not conform to the ' + + 'ICommentIcon interface', + ); + } + comment = this.addIcon(icon); } eventUtils.disable(); comment.setText(text); eventUtils.enable(); } else { - this.removeIcon(CommentIcon.TYPE); + this.removeIcon(IconType.COMMENT); } eventUtils.fire( diff --git a/core/icons/icon_types.ts b/core/icons/icon_types.ts index 25773c5bb..c5edb0f74 100644 --- a/core/icons/icon_types.ts +++ b/core/icons/icon_types.ts @@ -4,8 +4,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +import {ICommentIcon} from '../interfaces/i_comment_icon.js'; import {IIcon} from '../interfaces/i_icon.js'; -import {CommentIcon} from './comment_icon.js'; import {MutatorIcon} from './mutator_icon.js'; import {WarningIcon} from './warning_icon.js'; @@ -28,5 +28,5 @@ export class IconType<_T extends IIcon> { static MUTATOR = new IconType('mutator'); static WARNING = new IconType('warning'); - static COMMENT = new IconType('comment'); + static COMMENT = new IconType('comment'); } diff --git a/core/interfaces/i_comment_icon.ts b/core/interfaces/i_comment_icon.ts new file mode 100644 index 000000000..d1e18534f --- /dev/null +++ b/core/interfaces/i_comment_icon.ts @@ -0,0 +1,33 @@ +/** + * @license + * Copyright 2024 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {IconType} from '../icons.js'; +import {IIcon, isIcon} from './i_icon.js'; +import {Size} from '../utils/size.js'; +import {IHasBubble, hasBubble} from './i_has_bubble.js'; + +export interface ICommentIcon extends IIcon, IHasBubble { + setText(text: string): void; + + getText(): string; + + setBubbleSize(size: Size): void; + + getBubbleSize(): Size; +} + +/** Checks whether the given object is an ICommentIcon. */ +export function isCommentIcon(obj: Object): obj is ICommentIcon { + return ( + isIcon(obj) && + hasBubble(obj) && + (obj as any)['setText'] !== undefined && + (obj as any)['getText'] !== undefined && + (obj as any)['setBubbleSize'] !== undefined && + (obj as any)['getBubbleSize'] !== undefined && + obj.getType() === IconType.COMMENT + ); +} diff --git a/tests/mocha/block_test.js b/tests/mocha/block_test.js index c57ccbb15..3184d409d 100644 --- a/tests/mocha/block_test.js +++ b/tests/mocha/block_test.js @@ -19,6 +19,7 @@ import { createMockEvent, } from './test_helpers/events.js'; import {MockIcon, MockBubbleIcon} from './test_helpers/icon_mocks.js'; +import {IconType} from '../../build/src/core/icons/icon_types.js'; suite('Blocks', function () { setup(function () { @@ -1367,6 +1368,93 @@ suite('Blocks', function () { }); }); }); + + suite('Constructing registered comment classes', function () { + class MockComment extends MockIcon { + getType() { + return Blockly.icons.IconType.COMMENT; + } + + setText() {} + + getText() { + return ''; + } + + setBubbleSize() {} + + getBubbleSize() { + return Blockly.utils.Size(0, 0); + } + + bubbleIsVisible() { + return true; + } + + setBubbleVisible() {} + } + + setup(function () { + this.workspace = Blockly.inject('blocklyDiv', {}); + + this.block = this.workspace.newBlock('stack_block'); + this.block.initSvg(); + this.block.render(); + }); + + teardown(function () { + workspaceTeardown.call(this, this.workspace); + + Blockly.icons.registry.unregister( + Blockly.icons.IconType.COMMENT.toString(), + ); + Blockly.icons.registry.register( + Blockly.icons.IconType.COMMENT, + Blockly.icons.CommentIcon, + ); + }); + + test('setCommentText constructs the registered comment icon', function () { + Blockly.icons.registry.unregister( + Blockly.icons.IconType.COMMENT.toString(), + ); + Blockly.icons.registry.register( + Blockly.icons.IconType.COMMENT, + MockComment, + ); + + this.block.setCommentText('test text'); + + chai.assert.instanceOf( + this.block.getIcon(Blockly.icons.IconType.COMMENT), + MockComment, + ); + }); + + test('setCommentText throws if no icon is registered', function () { + Blockly.icons.registry.unregister( + Blockly.icons.IconType.COMMENT.toString(), + ); + + chai.assert.throws(() => { + this.block.setCommentText('test text'); + }, 'No comment icon class is registered, so a comment cannot be set'); + }); + + test('setCommentText throws if the icon is not an ICommentIcon', function () { + Blockly.icons.registry.unregister( + Blockly.icons.IconType.COMMENT.toString(), + ); + Blockly.icons.registry.register( + Blockly.icons.IconType.COMMENT, + MockIcon, + ); + + chai.assert.throws(() => { + this.block.setCommentText('test text'); + }, 'The class registered as a comment icon does not conform to the ICommentIcon interface'); + }); + }); }); suite('Getting/Setting Field (Values)', function () { From b70da6d3dee7788e5ad8ff4ad6615a14c5d0941f Mon Sep 17 00:00:00 2001 From: Rachel Fenichel Date: Fri, 15 Mar 2024 11:24:41 -0700 Subject: [PATCH 24/92] chore: delete blockfactory_old (#7933) --- demos/blockfactory_old/blocks.js | 794 ----------------------------- demos/blockfactory_old/factory.js | 819 ------------------------------ demos/blockfactory_old/icon.png | Bin 3634 -> 0 bytes demos/blockfactory_old/index.html | 224 -------- demos/blockfactory_old/link.png | Bin 228 -> 0 bytes 5 files changed, 1837 deletions(-) delete mode 100644 demos/blockfactory_old/blocks.js delete mode 100644 demos/blockfactory_old/factory.js delete mode 100644 demos/blockfactory_old/icon.png delete mode 100644 demos/blockfactory_old/index.html delete mode 100644 demos/blockfactory_old/link.png diff --git a/demos/blockfactory_old/blocks.js b/demos/blockfactory_old/blocks.js deleted file mode 100644 index 9d6adc9e8..000000000 --- a/demos/blockfactory_old/blocks.js +++ /dev/null @@ -1,794 +0,0 @@ -/** - * @license - * Copyright 2012 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview Blocks for Blockly's Block Factory application. - */ -'use strict'; - -Blockly.Blocks['factory_base'] = { - // Base of new block. - init: function() { - this.setColour(120); - this.appendDummyInput() - .appendField('name') - .appendField(new Blockly.FieldTextInput('block_type'), 'NAME'); - this.appendStatementInput('INPUTS') - .setCheck('Input') - .appendField('inputs'); - var dropdown = new Blockly.FieldDropdown([ - ['automatic inputs', 'AUTO'], - ['external inputs', 'EXT'], - ['inline inputs', 'INT']]); - this.appendDummyInput() - .appendField(dropdown, 'INLINE'); - dropdown = new Blockly.FieldDropdown([ - ['no connections', 'NONE'], - ['← left output', 'LEFT'], - ['↕ top+bottom connections', 'BOTH'], - ['↑ top connection', 'TOP'], - ['↓ bottom connection', 'BOTTOM']], - function(option) { - this.sourceBlock_.updateShape_(option); - // Connect a shadow block to this new input. - this.sourceBlock_.spawnOutputShadow_(option); - }); - this.appendDummyInput() - .appendField(dropdown, 'CONNECTIONS'); - this.appendValueInput('COLOUR') - .setCheck('Colour') - .appendField('colour'); - this.setTooltip('Build a custom block by plugging\n' + - 'fields, inputs and other blocks here.'); - this.setHelpUrl( - 'https://developers.google.com/blockly/guides/create-custom-blocks/block-factory'); - }, - mutationToDom: function() { - var container = Blockly.utils.xml.createElement('mutation'); - container.setAttribute('connections', this.getFieldValue('CONNECTIONS')); - return container; - }, - domToMutation: function(xmlElement) { - var connections = xmlElement.getAttribute('connections'); - this.updateShape_(connections); - }, - spawnOutputShadow_: function(option) { - // Helper method for deciding which type of outputs this block needs - // to attach shadow blocks to. - switch (option) { - case 'LEFT': - this.connectOutputShadow_('OUTPUTTYPE'); - break; - case 'TOP': - this.connectOutputShadow_('TOPTYPE'); - break; - case 'BOTTOM': - this.connectOutputShadow_('BOTTOMTYPE'); - break; - case 'BOTH': - this.connectOutputShadow_('TOPTYPE'); - this.connectOutputShadow_('BOTTOMTYPE'); - break; - } - }, - connectOutputShadow_: function(outputType) { - // Helper method to create & connect shadow block. - var type = this.workspace.newBlock('type_null'); - type.setShadow(true); - type.outputConnection.connect(this.getInput(outputType).connection); - type.initSvg(); - type.render(); - }, - updateShape_: function(option) { - var outputExists = this.getInput('OUTPUTTYPE'); - var topExists = this.getInput('TOPTYPE'); - var bottomExists = this.getInput('BOTTOMTYPE'); - if (option === 'LEFT') { - if (!outputExists) { - this.addTypeInput_('OUTPUTTYPE', 'output type'); - } - } else if (outputExists) { - this.removeInput('OUTPUTTYPE'); - } - if (option === 'TOP' || option === 'BOTH') { - if (!topExists) { - this.addTypeInput_('TOPTYPE', 'top type'); - } - } else if (topExists) { - this.removeInput('TOPTYPE'); - } - if (option === 'BOTTOM' || option === 'BOTH') { - if (!bottomExists) { - this.addTypeInput_('BOTTOMTYPE', 'bottom type'); - } - } else if (bottomExists) { - this.removeInput('BOTTOMTYPE'); - } - }, - addTypeInput_: function(name, label) { - this.appendValueInput(name) - .setCheck('Type') - .appendField(label); - this.moveInputBefore(name, 'COLOUR'); - } -}; - -var FIELD_MESSAGE = 'fields %1 %2'; -var FIELD_ARGS = [ - { - "type": "field_dropdown", - "name": "ALIGN", - "options": [['left', 'LEFT'], ['right', 'RIGHT'], ['centre', 'CENTRE']], - }, - { - "type": "input_statement", - "name": "FIELDS", - "check": "Field" - } -]; - -var TYPE_MESSAGE = 'type %1'; -var TYPE_ARGS = [ - { - "type": "input_value", - "name": "TYPE", - "check": "Type", - "align": "RIGHT" - } -]; - -Blockly.Blocks['input_value'] = { - // Value input. - init: function() { - this.jsonInit({ - "message0": "value input %1 %2", - "args0": [ - { - "type": "field_input", - "name": "INPUTNAME", - "text": "NAME" - }, - { - "type": "input_dummy" - } - ], - "message1": FIELD_MESSAGE, - "args1": FIELD_ARGS, - "message2": TYPE_MESSAGE, - "args2": TYPE_ARGS, - "previousStatement": "Input", - "nextStatement": "Input", - "colour": 210, - "tooltip": "A value socket for horizontal connections.", - "helpUrl": "https://www.youtube.com/watch?v=s2_xaEvcVI0#t=71" - }); - }, - onchange: function() { - inputNameCheck(this); - } -}; - -Blockly.Blocks['input_statement'] = { - // Statement input. - init: function() { - this.jsonInit({ - "message0": "statement input %1 %2", - "args0": [ - { - "type": "field_input", - "name": "INPUTNAME", - "text": "NAME" - }, - { - "type": "input_dummy" - }, - ], - "message1": FIELD_MESSAGE, - "args1": FIELD_ARGS, - "message2": TYPE_MESSAGE, - "args2": TYPE_ARGS, - "previousStatement": "Input", - "nextStatement": "Input", - "colour": 210, - "tooltip": "A statement socket for enclosed vertical stacks.", - "helpUrl": "https://www.youtube.com/watch?v=s2_xaEvcVI0#t=246" - }); - }, - onchange: function() { - inputNameCheck(this); - } -}; - -Blockly.Blocks['input_dummy'] = { - // Dummy input. - init: function() { - this.jsonInit({ - "message0": "dummy input", - "message1": FIELD_MESSAGE, - "args1": FIELD_ARGS, - "previousStatement": "Input", - "nextStatement": "Input", - "colour": 210, - "tooltip": "For adding fields on a separate row with no " + - "connections. Alignment options (left, right, centre) " + - "apply only to multi-line fields.", - "helpUrl": "https://www.youtube.com/watch?v=s2_xaEvcVI0#t=293" - }); - } -}; - -Blockly.Blocks['field_static'] = { - // Text value. - init: function() { - this.setColour(160); - this.appendDummyInput() - .appendField('text') - .appendField(new Blockly.FieldTextInput(''), 'TEXT'); - this.setPreviousStatement(true, 'Field'); - this.setNextStatement(true, 'Field'); - this.setTooltip('Static text that serves as a label.'); - this.setHelpUrl('https://www.youtube.com/watch?v=s2_xaEvcVI0#t=88'); - } -}; - -Blockly.Blocks['field_input'] = { - // Text input. - init: function() { - this.setColour(160); - this.appendDummyInput() - .appendField('text input') - .appendField(new Blockly.FieldTextInput('default'), 'TEXT') - .appendField(',') - .appendField(new Blockly.FieldTextInput('NAME'), 'FIELDNAME'); - this.setPreviousStatement(true, 'Field'); - this.setNextStatement(true, 'Field'); - this.setTooltip('An input field for the user to enter text.'); - this.setHelpUrl('https://www.youtube.com/watch?v=s2_xaEvcVI0#t=319'); - }, - onchange: function() { - fieldNameCheck(this); - } -}; - -Blockly.Blocks['field_number'] = { - // Numeric input. - init: function() { - this.setColour(160); - this.appendDummyInput() - .appendField('numeric input') - .appendField(new Blockly.FieldNumber(0), 'VALUE') - .appendField(',') - .appendField(new Blockly.FieldTextInput('NAME'), 'FIELDNAME'); - this.appendDummyInput() - .appendField('min') - .appendField(new Blockly.FieldNumber(-Infinity), 'MIN') - .appendField('max') - .appendField(new Blockly.FieldNumber(Infinity), 'MAX') - .appendField('precision') - .appendField(new Blockly.FieldNumber(0, 0), 'PRECISION'); - this.setPreviousStatement(true, 'Field'); - this.setNextStatement(true, 'Field'); - this.setTooltip('An input field for the user to enter a number.'); - this.setHelpUrl('https://www.youtube.com/watch?v=s2_xaEvcVI0#t=319'); - }, - onchange: function() { - fieldNameCheck(this); - } -}; - -Blockly.Blocks['field_angle'] = { - // Angle input. - init: function() { - this.setColour(160); - this.appendDummyInput() - .appendField('angle input') - .appendField(new Blockly.FieldAngle('90'), 'ANGLE') - .appendField(',') - .appendField(new Blockly.FieldTextInput('NAME'), 'FIELDNAME'); - this.setPreviousStatement(true, 'Field'); - this.setNextStatement(true, 'Field'); - this.setTooltip('An input field for the user to enter an angle.'); - this.setHelpUrl('https://www.youtube.com/watch?v=s2_xaEvcVI0#t=372'); - }, - onchange: function() { - fieldNameCheck(this); - } -}; - -Blockly.Blocks['field_dropdown'] = { - // Dropdown menu. - init: function() { - this.appendDummyInput() - .appendField('dropdown') - .appendField(new Blockly.FieldTextInput('NAME'), 'FIELDNAME'); - this.optionCount_ = 3; - this.updateShape_(); - this.setPreviousStatement(true, 'Field'); - this.setNextStatement(true, 'Field'); - this.setMutator(new Blockly.icons.MutatorIcon(['field_dropdown_option'])); - this.setColour(160); - this.setTooltip('Dropdown menu with a list of options.'); - this.setHelpUrl('https://www.youtube.com/watch?v=s2_xaEvcVI0#t=386'); - }, - mutationToDom: function(workspace) { - // Create XML to represent menu options. - var container = Blockly.utils.xml.createElement('mutation'); - container.setAttribute('options', this.optionCount_); - return container; - }, - domToMutation: function(container) { - // Parse XML to restore the menu options. - this.optionCount_ = parseInt(container.getAttribute('options'), 10); - this.updateShape_(); - }, - decompose: function(workspace) { - // Populate the mutator's dialog with this block's components. - var containerBlock = workspace.newBlock('field_dropdown_container'); - containerBlock.initSvg(); - var connection = containerBlock.getInput('STACK').connection; - for (var i = 0; i < this.optionCount_; i++) { - var optionBlock = workspace.newBlock('field_dropdown_option'); - optionBlock.initSvg(); - connection.connect(optionBlock.previousConnection); - connection = optionBlock.nextConnection; - } - return containerBlock; - }, - compose: function(containerBlock) { - // Reconfigure this block based on the mutator dialog's components. - var optionBlock = containerBlock.getInputTargetBlock('STACK'); - // Count number of inputs. - var data = []; - while (optionBlock) { - data.push([optionBlock.userData_, optionBlock.cpuData_]); - optionBlock = optionBlock.nextConnection && - optionBlock.nextConnection.targetBlock(); - } - this.optionCount_ = data.length; - this.updateShape_(); - // Restore any data. - for (var i = 0; i < this.optionCount_; i++) { - this.setFieldValue(data[i][0] || 'option', 'USER' + i); - this.setFieldValue(data[i][1] || 'OPTIONNAME', 'CPU' + i); - } - }, - saveConnections: function(containerBlock) { - // Store names and values for each option. - var optionBlock = containerBlock.getInputTargetBlock('STACK'); - var i = 0; - while (optionBlock) { - optionBlock.userData_ = this.getFieldValue('USER' + i); - optionBlock.cpuData_ = this.getFieldValue('CPU' + i); - i++; - optionBlock = optionBlock.nextConnection && - optionBlock.nextConnection.targetBlock(); - } - }, - updateShape_: function() { - // Modify this block to have the correct number of options. - // Add new options. - for (var i = 0; i < this.optionCount_; i++) { - if (!this.getInput('OPTION' + i)) { - this.appendDummyInput('OPTION' + i) - .appendField(new Blockly.FieldTextInput('option'), 'USER' + i) - .appendField(',') - .appendField(new Blockly.FieldTextInput('OPTIONNAME'), 'CPU' + i); - } - } - // Remove deleted options. - while (this.getInput('OPTION' + i)) { - this.removeInput('OPTION' + i); - i++; - } - }, - onchange: function() { - if (this.workspace && this.optionCount_ < 1) { - this.setWarningText('Drop down menu must\nhave at least one option.'); - } else { - fieldNameCheck(this); - } - } -}; - -Blockly.Blocks['field_dropdown_container'] = { - // Container. - init: function() { - this.setColour(160); - this.appendDummyInput() - .appendField('add options'); - this.appendStatementInput('STACK'); - this.setTooltip('Add, remove, or reorder options\n' + - 'to reconfigure this dropdown menu.'); - this.setHelpUrl('https://www.youtube.com/watch?v=s2_xaEvcVI0#t=386'); - this.contextMenu = false; - } -}; - -Blockly.Blocks['field_dropdown_option'] = { - // Add option. - init: function() { - this.setColour(160); - this.appendDummyInput() - .appendField('option'); - this.setPreviousStatement(true); - this.setNextStatement(true); - this.setTooltip('Add a new option to the dropdown menu.'); - this.setHelpUrl('https://www.youtube.com/watch?v=s2_xaEvcVI0#t=386'); - this.contextMenu = false; - } -}; - -Blockly.Blocks['field_checkbox'] = { - // Checkbox. - init: function() { - this.setColour(160); - this.appendDummyInput() - .appendField('checkbox') - .appendField(new Blockly.FieldCheckbox('TRUE'), 'CHECKED') - .appendField(',') - .appendField(new Blockly.FieldTextInput('NAME'), 'FIELDNAME'); - this.setPreviousStatement(true, 'Field'); - this.setNextStatement(true, 'Field'); - this.setTooltip('Checkbox field.'); - this.setHelpUrl('https://www.youtube.com/watch?v=s2_xaEvcVI0#t=485'); - }, - onchange: function() { - fieldNameCheck(this); - } -}; - -Blockly.Blocks['field_colour'] = { - // Colour input. - init: function() { - this.setColour(160); - this.appendDummyInput() - .appendField('colour') - .appendField(new Blockly.FieldColour('#ff0000'), 'COLOUR') - .appendField(',') - .appendField(new Blockly.FieldTextInput('NAME'), 'FIELDNAME'); - this.setPreviousStatement(true, 'Field'); - this.setNextStatement(true, 'Field'); - this.setTooltip('Colour input field.'); - this.setHelpUrl('https://www.youtube.com/watch?v=s2_xaEvcVI0#t=495'); - }, - onchange: function() { - fieldNameCheck(this); - } -}; - -Blockly.Blocks['field_variable'] = { - // Dropdown for variables. - init: function() { - this.setColour(160); - this.appendDummyInput() - .appendField('variable') - .appendField(new Blockly.FieldTextInput('item'), 'TEXT') - .appendField(',') - .appendField(new Blockly.FieldTextInput('NAME'), 'FIELDNAME'); - this.setPreviousStatement(true, 'Field'); - this.setNextStatement(true, 'Field'); - this.setTooltip('Dropdown menu for variable names.'); - this.setHelpUrl('https://www.youtube.com/watch?v=s2_xaEvcVI0#t=510'); - }, - onchange: function() { - fieldNameCheck(this); - } -}; - -Blockly.Blocks['field_image'] = { - // Image. - init: function() { - this.setColour(160); - var src = 'https://www.gstatic.com/codesite/ph/images/star_on.gif'; - this.appendDummyInput() - .appendField('image') - .appendField(new Blockly.FieldTextInput(src), 'SRC'); - this.appendDummyInput() - .appendField('width') - .appendField(new Blockly.FieldNumber('15', 0, NaN, 1), 'WIDTH') - .appendField('height') - .appendField(new Blockly.FieldNumber('15', 0, NaN, 1), 'HEIGHT') - .appendField('alt text') - .appendField(new Blockly.FieldTextInput('*'), 'ALT'); - this.setPreviousStatement(true, 'Field'); - this.setNextStatement(true, 'Field'); - this.setTooltip('Static image (JPEG, PNG, GIF, SVG, BMP).\n' + - 'Retains aspect ratio regardless of height and width.\n' + - 'Alt text is for when collapsed.'); - this.setHelpUrl('https://www.youtube.com/watch?v=s2_xaEvcVI0#t=567'); - } -}; - -Blockly.Blocks['type_group'] = { - // Group of types. - init: function() { - this.typeCount_ = 2; - this.updateShape_(); - this.setOutput(true, 'Type'); - this.setMutator(new Blockly.icons.MutatorIcon(['type_group_item'])); - this.setColour(230); - this.setTooltip('Allows more than one type to be accepted.'); - this.setHelpUrl('https://www.youtube.com/watch?v=s2_xaEvcVI0#t=677'); - }, - mutationToDom: function(workspace) { - // Create XML to represent a group of types. - var container = Blockly.utils.xml.createElement('mutation'); - container.setAttribute('types', this.typeCount_); - return container; - }, - domToMutation: function(container) { - // Parse XML to restore the group of types. - this.typeCount_ = parseInt(container.getAttribute('types'), 10); - this.updateShape_(); - for (var i = 0; i < this.typeCount_; i++) { - this.removeInput('TYPE' + i); - } - for (var i = 0; i < this.typeCount_; i++) { - var input = this.appendValueInput('TYPE' + i) - .setCheck('Type'); - if (i === 0) { - input.appendField('any of'); - } - } - }, - decompose: function(workspace) { - // Populate the mutator's dialog with this block's components. - var containerBlock = workspace.newBlock('type_group_container'); - containerBlock.initSvg(); - var connection = containerBlock.getInput('STACK').connection; - for (var i = 0; i < this.typeCount_; i++) { - var typeBlock = workspace.newBlock('type_group_item'); - typeBlock.initSvg(); - connection.connect(typeBlock.previousConnection); - connection = typeBlock.nextConnection; - } - return containerBlock; - }, - compose: function(containerBlock) { - // Reconfigure this block based on the mutator dialog's components. - var typeBlock = containerBlock.getInputTargetBlock('STACK'); - // Count number of inputs. - var connections = []; - while (typeBlock) { - connections.push(typeBlock.valueConnection_); - typeBlock = typeBlock.nextConnection && - typeBlock.nextConnection.targetBlock(); - } - // Disconnect any children that don't belong. - for (var i = 0; i < this.typeCount_; i++) { - var connection = this.getInput('TYPE' + i).connection.targetConnection; - if (connection && connections.indexOf(connection) === -1) { - connection.disconnect(); - } - } - this.typeCount_ = connections.length; - this.updateShape_(); - // Reconnect any child blocks. - for (var i = 0; i < this.typeCount_; i++) { - connections[i]?.reconnect(this, 'TYPE' + i); - } - }, - saveConnections: function(containerBlock) { - // Store a pointer to any connected child blocks. - var typeBlock = containerBlock.getInputTargetBlock('STACK'); - var i = 0; - while (typeBlock) { - var input = this.getInput('TYPE' + i); - typeBlock.valueConnection_ = input && input.connection.targetConnection; - i++; - typeBlock = typeBlock.nextConnection && - typeBlock.nextConnection.targetBlock(); - } - }, - updateShape_: function() { - // Modify this block to have the correct number of inputs. - // Add new inputs. - for (var i = 0; i < this.typeCount_; i++) { - if (!this.getInput('TYPE' + i)) { - var input = this.appendValueInput('TYPE' + i); - if (i === 0) { - input.appendField('any of'); - } - } - } - // Remove deleted inputs. - while (this.getInput('TYPE' + i)) { - this.removeInput('TYPE' + i); - i++; - } - } -}; - -Blockly.Blocks['type_group_container'] = { - // Container. - init: function() { - this.jsonInit({ - "message0": "add types %1 %2", - "args0": [ - {"type": "input_dummy"}, - {"type": "input_statement", "name": "STACK"} - ], - "colour": 230, - "tooltip": "Add, or remove allowed type.", - "helpUrl": "https://www.youtube.com/watch?v=s2_xaEvcVI0#t=677" - }); - } -}; - -Blockly.Blocks['type_group_item'] = { - // Add type. - init: function() { - this.jsonInit({ - "message0": "type", - "previousStatement": null, - "nextStatement": null, - "colour": 230, - "tooltip": "Add a new allowed type.", - "helpUrl": "https://www.youtube.com/watch?v=s2_xaEvcVI0#t=677" - }); - } -}; - -Blockly.Blocks['type_null'] = { - // Null type. - valueType: null, - init: function() { - this.jsonInit({ - "message0": "any", - "output": "Type", - "colour": 230, - "tooltip": "Any type is allowed.", - "helpUrl": "https://www.youtube.com/watch?v=s2_xaEvcVI0#t=602" - }); - } -}; - -Blockly.Blocks['type_boolean'] = { - // Boolean type. - valueType: 'Boolean', - init: function() { - this.jsonInit({ - "message0": "Boolean", - "output": "Type", - "colour": 230, - "tooltip": "Booleans (true/false) are allowed.", - "helpUrl": "https://www.youtube.com/watch?v=s2_xaEvcVI0#t=602" - }); - } -}; - -Blockly.Blocks['type_number'] = { - // Number type. - valueType: 'Number', - init: function() { - this.jsonInit({ - "message0": "Number", - "output": "Type", - "colour": 230, - "tooltip": "Numbers (int/float) are allowed.", - "helpUrl": "https://www.youtube.com/watch?v=s2_xaEvcVI0#t=602" - }); - } -}; - -Blockly.Blocks['type_string'] = { - // String type. - valueType: 'String', - init: function() { - this.jsonInit({ - "message0": "String", - "output": "Type", - "colour": 230, - "tooltip": "Strings (text) are allowed.", - "helpUrl": "https://www.youtube.com/watch?v=s2_xaEvcVI0#t=602" - }); - } -}; - -Blockly.Blocks['type_list'] = { - // List type. - valueType: 'Array', - init: function() { - this.jsonInit({ - "message0": "Array", - "output": "Type", - "colour": 230, - "tooltip": "Arrays (lists) are allowed.", - "helpUrl": "https://www.youtube.com/watch?v=s2_xaEvcVI0#t=602" - }); - } -}; - -Blockly.Blocks['type_other'] = { - // Other type. - init: function() { - this.jsonInit({ - "message0": "other %1", - "args0": [{"type": "field_input", "name": "TYPE", "text": ""}], - "output": "Type", - "colour": 230, - "tooltip": "Custom type to allow.", - "helpUrl": "https://www.youtube.com/watch?v=s2_xaEvcVI0#t=702" - }); - } -}; - -Blockly.Blocks['colour_hue'] = { - // Set the colour of the block. - init: function() { - this.appendDummyInput() - .appendField('hue:') - .appendField(new Blockly.FieldAngle('0', this.validator), 'HUE'); - this.setOutput(true, 'Colour'); - this.setTooltip('Paint the block with this colour.'); - this.setHelpUrl('https://www.youtube.com/watch?v=s2_xaEvcVI0#t=55'); - }, - validator: function(text) { - // Update the current block's colour to match. - var hue = parseInt(text, 10); - if (!isNaN(hue)) { - this.sourceBlock_.setColour(hue); - } - }, - mutationToDom: function(workspace) { - var container = Blockly.utils.xml.createElement('mutation'); - container.setAttribute('colour', this.getColour()); - return container; - }, - domToMutation: function(container) { - this.setColour(container.getAttribute('colour')); - } -}; - -/** - * Check to see if more than one field has this name. - * Highly inefficient (On^2), but n is small. - * @param {!Blockly.Block} referenceBlock Block to check. - */ -function fieldNameCheck(referenceBlock) { - if (!referenceBlock.workspace) { - // Block has been deleted. - return; - } - var name = referenceBlock.getFieldValue('FIELDNAME').toLowerCase(); - var count = 0; - var blocks = referenceBlock.workspace.getAllBlocks(false); - for (var i = 0, block; block = blocks[i]; i++) { - var otherName = block.getFieldValue('FIELDNAME'); - if (!block.disabled && !block.getInheritedDisabled() && - otherName && otherName.toLowerCase() === name) { - count++; - } - } - var msg = (count > 1) ? - 'There are ' + count + ' field blocks\n with this name.' : null; - referenceBlock.setWarningText(msg); -} - -/** - * Check to see if more than one input has this name. - * Highly inefficient (On^2), but n is small. - * @param {!Blockly.Block} referenceBlock Block to check. - */ -function inputNameCheck(referenceBlock) { - if (!referenceBlock.workspace) { - // Block has been deleted. - return; - } - var name = referenceBlock.getFieldValue('INPUTNAME').toLowerCase(); - var count = 0; - var blocks = referenceBlock.workspace.getAllBlocks(false); - for (var i = 0, block; block = blocks[i]; i++) { - var otherName = block.getFieldValue('INPUTNAME'); - if (!block.disabled && !block.getInheritedDisabled() && - otherName && otherName.toLowerCase() === name) { - count++; - } - } - var msg = (count > 1) ? - 'There are ' + count + ' input blocks\n with this name.' : null; - referenceBlock.setWarningText(msg); -} diff --git a/demos/blockfactory_old/factory.js b/demos/blockfactory_old/factory.js deleted file mode 100644 index 7ac395032..000000000 --- a/demos/blockfactory_old/factory.js +++ /dev/null @@ -1,819 +0,0 @@ -/** - * @license - * Copyright 2012 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview JavaScript for Blockly's Block Factory application. - */ -'use strict'; - -/** - * Workspace for user to build block. - * @type {Blockly.Workspace} - */ -var mainWorkspace = null; - -/** - * Workspace for preview of block. - * @type {Blockly.Workspace} - */ -var previewWorkspace = null; - -/** - * Name of block if not named. - */ -var UNNAMED = 'unnamed'; - -/** - * Change the language code format. - */ -function formatChange() { - var mask = document.getElementById('blocklyMask'); - var languagePre = document.getElementById('languagePre'); - var languageTA = document.getElementById('languageTA'); - if (document.getElementById('format').value === 'Manual') { - Blockly.common.getMainWorkspace().hideChaff(); - mask.style.display = 'block'; - languagePre.style.display = 'none'; - languageTA.style.display = 'block'; - var code = languagePre.textContent.trim(); - languageTA.value = code; - languageTA.focus(); - updatePreview(); - } else { - mask.style.display = 'none'; - languageTA.style.display = 'none'; - languagePre.style.display = 'block'; - updateLanguage(); - } - disableEnableLink(); -} - -/** - * Update the language code based on constructs made in Blockly. - */ -function updateLanguage() { - var rootBlock = getRootBlock(); - if (!rootBlock) { - return; - } - var blockType = rootBlock.getFieldValue('NAME').trim().toLowerCase(); - if (!blockType) { - blockType = UNNAMED; - } - blockType = blockType.replace(/\W/g, '_').replace(/^(\d)/, '_\\1'); - switch (document.getElementById('format').value) { - case 'JSON': - var code = formatJson_(blockType, rootBlock); - break; - case 'JavaScript': - var code = formatJavaScript_(blockType, rootBlock); - break; - } - injectCode(code, 'languagePre'); - updatePreview(); -} - -/** - * Update the language code as JSON. - * @param {string} blockType Name of block. - * @param {!Blockly.Block} rootBlock Factory_base block. - * @return {string} Generated language code. - * @private - */ -function formatJson_(blockType, rootBlock) { - var JS = {}; - // Type is not used by Blockly, but may be used by a loader. - JS.type = blockType; - // Generate inputs. - var message = []; - var args = []; - var contentsBlock = rootBlock.getInputTargetBlock('INPUTS'); - var lastInput = null; - while (contentsBlock) { - if (!contentsBlock.disabled && !contentsBlock.getInheritedDisabled()) { - var fields = getFieldsJson_(contentsBlock.getInputTargetBlock('FIELDS')); - for (var i = 0; i < fields.length; i++) { - if (typeof fields[i] === 'string') { - message.push(fields[i].replace(/%/g, '%%')); - } else { - args.push(fields[i]); - message.push('%' + args.length); - } - } - - var input = {type: contentsBlock.type}; - // Dummy inputs don't have names. Other inputs do. - if (contentsBlock.type !== 'input_dummy') { - input.name = contentsBlock.getFieldValue('INPUTNAME'); - } - var check = JSON.parse(getOptTypesFrom(contentsBlock, 'TYPE') || 'null'); - if (check) { - input.check = check; - } - var align = contentsBlock.getFieldValue('ALIGN'); - if (align !== 'LEFT') { - input.align = align; - } - args.push(input); - message.push('%' + args.length); - lastInput = contentsBlock; - } - contentsBlock = contentsBlock.nextConnection && - contentsBlock.nextConnection.targetBlock(); - } - // Remove last input if dummy and not empty. - if (lastInput && lastInput.type === 'input_dummy') { - var fields = lastInput.getInputTargetBlock('FIELDS'); - if (fields && getFieldsJson_(fields).join('').trim() !== '') { - var align = lastInput.getFieldValue('ALIGN'); - if (align !== 'LEFT') { - JS.lastDummyAlign0 = align; - } - args.pop(); - message.pop(); - } - } - JS.message0 = message.join(' '); - if (args.length) { - JS.args0 = args; - } - // Generate inline/external switch. - if (rootBlock.getFieldValue('INLINE') === 'EXT') { - JS.inputsInline = false; - } else if (rootBlock.getFieldValue('INLINE') === 'INT') { - JS.inputsInline = true; - } - // Generate output, or next/previous connections. - switch (rootBlock.getFieldValue('CONNECTIONS')) { - case 'LEFT': - JS.output = - JSON.parse(getOptTypesFrom(rootBlock, 'OUTPUTTYPE') || 'null'); - break; - case 'BOTH': - JS.previousStatement = - JSON.parse(getOptTypesFrom(rootBlock, 'TOPTYPE') || 'null'); - JS.nextStatement = - JSON.parse(getOptTypesFrom(rootBlock, 'BOTTOMTYPE') || 'null'); - break; - case 'TOP': - JS.previousStatement = - JSON.parse(getOptTypesFrom(rootBlock, 'TOPTYPE') || 'null'); - break; - case 'BOTTOM': - JS.nextStatement = - JSON.parse(getOptTypesFrom(rootBlock, 'BOTTOMTYPE') || 'null'); - break; - } - // Generate colour. - var colourBlock = rootBlock.getInputTargetBlock('COLOUR'); - if (colourBlock && !colourBlock.disabled) { - var hue = parseInt(colourBlock.getFieldValue('HUE'), 10); - JS.colour = hue; - } - JS.tooltip = ''; - JS.helpUrl = 'http://www.example.com/'; - return JSON.stringify(JS, null, ' '); -} - -/** - * Update the language code as JavaScript. - * @param {string} blockType Name of block. - * @param {!Blockly.Block} rootBlock Factory_base block. - * @return {string} Generated language code. - * @private - */ -function formatJavaScript_(blockType, rootBlock) { - var code = []; - code.push("Blockly.Blocks['" + blockType + "'] = {"); - code.push(" init: function() {"); - // Generate inputs. - var TYPES = {'input_value': 'appendValueInput', - 'input_statement': 'appendStatementInput', - 'input_dummy': 'appendDummyInput'}; - var contentsBlock = rootBlock.getInputTargetBlock('INPUTS'); - while (contentsBlock) { - if (!contentsBlock.disabled && !contentsBlock.getInheritedDisabled()) { - var name = ''; - // Dummy inputs don't have names. Other inputs do. - if (contentsBlock.type !== 'input_dummy') { - name = escapeString(contentsBlock.getFieldValue('INPUTNAME')); - } - code.push(' this.' + TYPES[contentsBlock.type] + '(' + name + ')'); - var check = getOptTypesFrom(contentsBlock, 'TYPE'); - if (check) { - code.push(' .setCheck(' + check + ')'); - } - var align = contentsBlock.getFieldValue('ALIGN'); - if (align !== 'LEFT') { - code.push(' .setAlign(Blockly.ALIGN_' + align + ')'); - } - var fields = getFieldsJs_(contentsBlock.getInputTargetBlock('FIELDS')); - for (var i = 0; i < fields.length; i++) { - code.push(' .appendField(' + fields[i] + ')'); - } - // Add semicolon to last line to finish the statement. - code[code.length - 1] += ';'; - } - contentsBlock = contentsBlock.nextConnection && - contentsBlock.nextConnection.targetBlock(); - } - // Generate inline/external switch. - if (rootBlock.getFieldValue('INLINE') === 'EXT') { - code.push(' this.setInputsInline(false);'); - } else if (rootBlock.getFieldValue('INLINE') === 'INT') { - code.push(' this.setInputsInline(true);'); - } - // Generate output, or next/previous connections. - switch (rootBlock.getFieldValue('CONNECTIONS')) { - case 'LEFT': - code.push(connectionLineJs_('setOutput', 'OUTPUTTYPE')); - break; - case 'BOTH': - code.push(connectionLineJs_('setPreviousStatement', 'TOPTYPE')); - code.push(connectionLineJs_('setNextStatement', 'BOTTOMTYPE')); - break; - case 'TOP': - code.push(connectionLineJs_('setPreviousStatement', 'TOPTYPE')); - break; - case 'BOTTOM': - code.push(connectionLineJs_('setNextStatement', 'BOTTOMTYPE')); - break; - } - // Generate colour. - var colourBlock = rootBlock.getInputTargetBlock('COLOUR'); - if (colourBlock && !colourBlock.disabled) { - var hue = parseInt(colourBlock.getFieldValue('HUE'), 10); - if (!isNaN(hue)) { - code.push(' this.setColour(' + hue + ');'); - } - } - code.push(" this.setTooltip('');"); - code.push(" this.setHelpUrl('http://www.example.com/');"); - code.push(' }'); - code.push('};'); - return code.join('\n'); -} - -/** - * Create JS code required to create a top, bottom, or value connection. - * @param {string} functionName JavaScript function name. - * @param {string} typeName Name of type input. - * @return {string} Line of JavaScript code to create connection. - * @private - */ -function connectionLineJs_(functionName, typeName) { - var type = getOptTypesFrom(getRootBlock(), typeName); - if (type) { - type = ', ' + type; - } else { - type = ''; - } - return ' this.' + functionName + '(true' + type + ');'; -} - -/** - * Returns field strings and any config. - * @param {!Blockly.Block} block Input block. - * @return {!Array} Field strings. - * @private - */ -function getFieldsJs_(block) { - var fields = []; - while (block) { - if (!block.disabled && !block.getInheritedDisabled()) { - switch (block.type) { - case 'field_static': - // Result: 'hello' - fields.push(escapeString(block.getFieldValue('TEXT'))); - break; - case 'field_input': - // Result: new Blockly.FieldTextInput('Hello'), 'GREET' - fields.push('new Blockly.FieldTextInput(' + - escapeString(block.getFieldValue('TEXT')) + '), ' + - escapeString(block.getFieldValue('FIELDNAME'))); - break; - case 'field_number': - // Result: new Blockly.FieldNumber(10, 0, 100, 1), 'NUMBER' - var args = [ - Number(block.getFieldValue('VALUE')), - Number(block.getFieldValue('MIN')), - Number(block.getFieldValue('MAX')), - Number(block.getFieldValue('PRECISION')) - ]; - // Remove any trailing arguments that aren't needed. - if (args[3] === 0) { - args.pop(); - if (args[2] === Infinity) { - args.pop(); - if (args[1] === -Infinity) { - args.pop(); - } - } - } - fields.push('new Blockly.FieldNumber(' + args.join(', ') + '), ' + - escapeString(block.getFieldValue('FIELDNAME'))); - break; - case 'field_angle': - // Result: new Blockly.FieldAngle(90), 'ANGLE' - fields.push('new Blockly.FieldAngle(' + - Number(block.getFieldValue('ANGLE')) + '), ' + - escapeString(block.getFieldValue('FIELDNAME'))); - break; - case 'field_checkbox': - // Result: new Blockly.FieldCheckbox('TRUE'), 'CHECK' - fields.push('new Blockly.FieldCheckbox(' + - escapeString(block.getFieldValue('CHECKED')) + '), ' + - escapeString(block.getFieldValue('FIELDNAME'))); - break; - case 'field_colour': - // Result: new Blockly.FieldColour('#ff0000'), 'COLOUR' - fields.push('new Blockly.FieldColour(' + - escapeString(block.getFieldValue('COLOUR')) + '), ' + - escapeString(block.getFieldValue('FIELDNAME'))); - break; - case 'field_variable': - // Result: new Blockly.FieldVariable('item'), 'VAR' - var varname = escapeString(block.getFieldValue('TEXT') || null); - fields.push('new Blockly.FieldVariable(' + varname + '), ' + - escapeString(block.getFieldValue('FIELDNAME'))); - break; - case 'field_dropdown': - // Result: - // new Blockly.FieldDropdown([['yes', '1'], ['no', '0']]), 'TOGGLE' - var options = []; - for (var i = 0; i < block.optionCount_; i++) { - options[i] = '[' + escapeString(block.getFieldValue('USER' + i)) + - ', ' + escapeString(block.getFieldValue('CPU' + i)) + ']'; - } - if (options.length) { - fields.push('new Blockly.FieldDropdown([' + - options.join(', ') + ']), ' + - escapeString(block.getFieldValue('FIELDNAME'))); - } - break; - case 'field_image': - // Result: new Blockly.FieldImage('http://...', 80, 60, '*') - var src = escapeString(block.getFieldValue('SRC')); - var width = Number(block.getFieldValue('WIDTH')); - var height = Number(block.getFieldValue('HEIGHT')); - var alt = escapeString(block.getFieldValue('ALT')); - fields.push('new Blockly.FieldImage(' + - src + ', ' + width + ', ' + height + ', ' + alt + ')'); - break; - } - } - block = block.nextConnection && block.nextConnection.targetBlock(); - } - return fields; -} - -/** - * Returns field strings and any config. - * @param {!Blockly.Block} block Input block. - * @return {!Array} Array of static text and field configs. - * @private - */ -function getFieldsJson_(block) { - var fields = []; - while (block) { - if (!block.disabled && !block.getInheritedDisabled()) { - switch (block.type) { - case 'field_static': - // Result: 'hello' - fields.push(block.getFieldValue('TEXT')); - break; - case 'field_input': - fields.push({ - type: block.type, - name: block.getFieldValue('FIELDNAME'), - text: block.getFieldValue('TEXT') - }); - break; - case 'field_number': - var obj = { - type: block.type, - name: block.getFieldValue('FIELDNAME'), - value: Number(block.getFieldValue('VALUE')) - }; - var min = Number(block.getFieldValue('MIN')); - if (min > -Infinity) { - obj.min = min; - } - var max = Number(block.getFieldValue('MAX')); - if (max < Infinity) { - obj.max = max; - } - var precision = Number(block.getFieldValue('PRECISION')); - if (precision) { - obj.precision = precision; - } - fields.push(obj); - break; - case 'field_angle': - fields.push({ - type: block.type, - name: block.getFieldValue('FIELDNAME'), - angle: Number(block.getFieldValue('ANGLE')) - }); - break; - case 'field_checkbox': - fields.push({ - type: block.type, - name: block.getFieldValue('FIELDNAME'), - checked: block.getFieldValue('CHECKED') === 'TRUE' - }); - break; - case 'field_colour': - fields.push({ - type: block.type, - name: block.getFieldValue('FIELDNAME'), - colour: block.getFieldValue('COLOUR') - }); - break; - case 'field_variable': - fields.push({ - type: block.type, - name: block.getFieldValue('FIELDNAME'), - variable: block.getFieldValue('TEXT') || null - }); - break; - case 'field_dropdown': - var options = []; - for (var i = 0; i < block.optionCount_; i++) { - options[i] = [block.getFieldValue('USER' + i), - block.getFieldValue('CPU' + i)]; - } - if (options.length) { - fields.push({ - type: block.type, - name: block.getFieldValue('FIELDNAME'), - options: options - }); - } - break; - case 'field_image': - fields.push({ - type: block.type, - src: block.getFieldValue('SRC'), - width: Number(block.getFieldValue('WIDTH')), - height: Number(block.getFieldValue('HEIGHT')), - alt: block.getFieldValue('ALT') - }); - break; - } - } - block = block.nextConnection && block.nextConnection.targetBlock(); - } - return fields; -} - -/** - * Escape a string. - * @param {string} string String to escape. - * @return {string} Escaped string surrounded by quotes. - */ -function escapeString(string) { - return JSON.stringify(string); -} - -/** - * Fetch the type(s) defined in the given input. - * Format as a string for appending to the generated code. - * @param {!Blockly.Block} block Block with input. - * @param {string} name Name of the input. - * @return {?string} String defining the types. - */ -function getOptTypesFrom(block, name) { - var types = getTypesFrom_(block, name); - if (types.length === 0) { - return undefined; - } else if (types.indexOf('null') !== -1) { - return 'null'; - } else if (types.length === 1) { - return types[0]; - } else { - return '[' + types.join(', ') + ']'; - } -} - -/** - * Fetch the type(s) defined in the given input. - * @param {!Blockly.Block} block Block with input. - * @param {string} name Name of the input. - * @return {!Array} List of types. - * @private - */ -function getTypesFrom_(block, name) { - var typeBlock = block.getInputTargetBlock(name); - var types; - if (!typeBlock || typeBlock.disabled) { - types = []; - } else if (typeBlock.type === 'type_other') { - types = [escapeString(typeBlock.getFieldValue('TYPE'))]; - } else if (typeBlock.type === 'type_group') { - types = []; - for (var i = 0; i < typeBlock.typeCount_; i++) { - types = types.concat(getTypesFrom_(typeBlock, 'TYPE' + i)); - } - // Remove duplicates. - var hash = Object.create(null); - for (var n = types.length - 1; n >= 0; n--) { - if (hash[types[n]]) { - types.splice(n, 1); - } - hash[types[n]] = true; - } - } else { - types = [escapeString(typeBlock.valueType)]; - } - return types; -} - -/** - * Update the generator code. - * @param {!Blockly.Block} block Rendered block in preview workspace. - */ -function updateGenerator(block) { - function makeVar(root, name) { - name = name.toLowerCase().replace(/\W/g, '_'); - return ' var ' + root + '_' + name; - } - var language = document.getElementById('language').value; - var code = []; - code.push("Blockly." + language + "['" + block.type + - "'] = function(block) {"); - - // Generate getters for any fields or inputs. - for (var i = 0, input; input = block.inputList[i]; i++) { - for (var j = 0, field; field = input.fieldRow[j]; j++) { - var name = field.name; - if (!name) { - continue; - } - if (field instanceof Blockly.FieldVariable) { - // Subclass of Blockly.FieldDropdown, must test first. - code.push(makeVar('variable', name) + - " = Blockly." + language + - ".nameDB_.getName(block.getFieldValue('" + name + - "'), Blockly.Variables.NAME_TYPE);"); - } else if (field instanceof Blockly.FieldAngle) { - // Subclass of Blockly.FieldTextInput, must test first. - code.push(makeVar('angle', name) + - " = block.getFieldValue('" + name + "');"); - } else if (field instanceof Blockly.FieldColour) { - code.push(makeVar('colour', name) + - " = block.getFieldValue('" + name + "');"); - } else if (field instanceof Blockly.FieldCheckbox) { - code.push(makeVar('checkbox', name) + - " = block.getFieldValue('" + name + "') === 'TRUE';"); - } else if (field instanceof Blockly.FieldDropdown) { - code.push(makeVar('dropdown', name) + - " = block.getFieldValue('" + name + "');"); - } else if (field instanceof Blockly.FieldNumber) { - code.push(makeVar('number', name) + - " = block.getFieldValue('" + name + "');"); - } else if (field instanceof Blockly.FieldTextInput) { - code.push(makeVar('text', name) + - " = block.getFieldValue('" + name + "');"); - } - } - var name = input.name; - if (name) { - if (input.type === Blockly.INPUT_VALUE) { - code.push(makeVar('value', name) + - " = Blockly." + language + ".valueToCode(block, '" + name + - "', Blockly." + language + ".ORDER_ATOMIC);"); - } else if (input.type === Blockly.NEXT_STATEMENT) { - code.push(makeVar('statements', name) + - " = Blockly." + language + ".statementToCode(block, '" + - name + "');"); - } - } - } - // Most languages end lines with a semicolon. Python does not. - var lineEnd = { - 'JavaScript': ';', - 'Python': '', - 'PHP': ';', - 'Dart': ';' - }; - code.push(" // TODO: Assemble " + language + " into code variable."); - if (block.outputConnection) { - code.push(" var code = '...';"); - code.push(" // TODO: Change ORDER_NONE to the correct strength."); - code.push(" return [code, Blockly." + language + ".ORDER_NONE];"); - } else { - code.push(" var code = '..." + (lineEnd[language] || '') + "\\n';"); - code.push(" return code;"); - } - code.push("};"); - - injectCode(code.join('\n'), 'generatorPre'); -} - -/** - * Existing direction ('ltr' vs 'rtl') of preview. - */ -var oldDir = null; - -/** - * Update the preview display. - */ -function updatePreview() { - // Toggle between LTR/RTL if needed (also used in first display). - var newDir = document.getElementById('direction').value; - if (oldDir !== newDir) { - if (previewWorkspace) { - previewWorkspace.dispose(); - } - var rtl = newDir === 'rtl'; - previewWorkspace = Blockly.inject('preview', - {rtl: rtl, - media: '../../media/', - scrollbars: true}); - oldDir = newDir; - } - previewWorkspace.clear(); - - // Fetch the code and determine its format (JSON or JavaScript). - var format = document.getElementById('format').value; - if (format === 'Manual') { - var code = document.getElementById('languageTA').value; - // If the code is JSON, it will parse, otherwise treat as JS. - try { - JSON.parse(code); - format = 'JSON'; - } catch (e) { - format = 'JavaScript'; - } - } else { - var code = document.getElementById('languagePre').textContent; - } - if (!code.trim()) { - // Nothing to render. Happens while cloud storage is loading. - return; - } - - // Backup Blockly.Blocks object so that main workspace and preview don't - // collide if user creates a 'factory_base' block, for instance. - var backupBlocks = Blockly.Blocks; - try { - // Make a shallow copy. - Blockly.Blocks = {}; - for (var prop in backupBlocks) { - Blockly.Blocks[prop] = backupBlocks[prop]; - } - - if (format === 'JSON') { - var json = JSON.parse(code); - Blockly.Blocks[json.type || UNNAMED] = { - init: function() { - this.jsonInit(json); - } - }; - } else if (format === 'JavaScript') { - eval(code); - } else { - throw 'Unknown format: ' + format; - } - - // Look for a block on Blockly.Blocks that does not match the backup. - var blockType = null; - for (var type in Blockly.Blocks) { - if (typeof Blockly.Blocks[type].init === 'function' && - Blockly.Blocks[type] !== backupBlocks[type]) { - blockType = type; - break; - } - } - if (!blockType) { - return; - } - - // Create the preview block. - var previewBlock = previewWorkspace.newBlock(blockType); - previewBlock.initSvg(); - previewBlock.render(); - previewBlock.setMovable(false); - previewBlock.setDeletable(false); - previewBlock.moveBy(15, 10); - previewWorkspace.clearUndo(); - - updateGenerator(previewBlock); - } finally { - Blockly.Blocks = backupBlocks; - } -} - -/** - * Inject code into a pre tag, with syntax highlighting. - * Safe from HTML/script injection. - * @param {string} code Lines of code. - * @param {string} id ID of
 element to inject into.
- */
-function injectCode(code, id) {
-  var pre = document.getElementById(id);
-  pre.textContent = code;
-  // Remove the 'prettyprinted' class, so that Prettify will recalculate.
-  pre.className = pre.className.replace('prettyprinted', '');
-  PR.prettyPrint();
-}
-
-/**
- * Return the uneditable container block that everything else attaches to.
- * @return {Blockly.Block}
- */
-function getRootBlock() {
-  var blocks = mainWorkspace.getTopBlocks(false);
-  for (var i = 0, block; block = blocks[i]; i++) {
-    if (block.type === 'factory_base') {
-      return block;
-    }
-  }
-  return null;
-}
-
-/**
- * Disable the link button if the format is 'Manual', enable otherwise.
- */
-function disableEnableLink() {
-  var linkButton = document.getElementById('linkButton');
-  linkButton.disabled = document.getElementById('format').value === 'Manual';
-}
-
-/**
- * Initialize Blockly and layout.  Called on page load.
- */
-function init() {
-  if ('BlocklyStorage' in window) {
-    BlocklyStorage.HTTPREQUEST_ERROR =
-        'There was a problem with the request.\n';
-    BlocklyStorage.LINK_ALERT =
-        'Share your blocks with this link:\n\n%1';
-    BlocklyStorage.HASH_ERROR =
-        'Sorry, "%1" doesn\'t correspond with any saved Blockly file.';
-    BlocklyStorage.XML_ERROR = 'Could not load your saved file.\n'+
-        'Perhaps it was created with a different version of Blockly?';
-    var linkButton = document.getElementById('linkButton');
-    linkButton.style.display = 'inline-block';
-    linkButton.addEventListener('click',
-        function() {BlocklyStorage.link(mainWorkspace);});
-    disableEnableLink();
-  }
-
-  document.getElementById('helpButton').addEventListener('click',
-    function() {
-      open('https://developers.google.com/blockly/guides/create-custom-blocks/block-factory',
-           'BlockFactoryHelp');
-    });
-
-  var expandList = [
-    document.getElementById('blockly'),
-    document.getElementById('blocklyMask'),
-    document.getElementById('preview'),
-    document.getElementById('languagePre'),
-    document.getElementById('languageTA'),
-    document.getElementById('generatorPre')
-  ];
-  var onresize = function(e) {
-    for (var i = 0, expand; expand = expandList[i]; i++) {
-      expand.style.width = (expand.parentNode.offsetWidth - 2) + 'px';
-      expand.style.height = (expand.parentNode.offsetHeight - 2) + 'px';
-    }
-  };
-  onresize();
-  window.addEventListener('resize', onresize);
-
-  var toolbox = document.getElementById('toolbox');
-  mainWorkspace = Blockly.inject('blockly',
-      {collapse: false,
-       toolbox: toolbox,
-       media: '../../media/'});
-
-  // Create the root block.
-  if ('BlocklyStorage' in window && window.location.hash.length > 1) {
-    BlocklyStorage.retrieveXml(window.location.hash.substring(1),
-                               mainWorkspace);
-  } else {
-    var xml = '';
-    Blockly.Xml.domToWorkspace(Blockly.utils.xml.textToDom(xml), mainWorkspace);
-  }
-  mainWorkspace.clearUndo();
-
-  mainWorkspace.addChangeListener(Blockly.Events.disableOrphans);
-  mainWorkspace.addChangeListener(updateLanguage);
-  document.getElementById('direction')
-      .addEventListener('change', updatePreview);
-  document.getElementById('languageTA')
-      .addEventListener('change', updatePreview);
-  document.getElementById('languageTA')
-      .addEventListener('keyup', updatePreview);
-  document.getElementById('format')
-      .addEventListener('change', formatChange);
-  document.getElementById('language')
-      .addEventListener('change', updatePreview);
-}
-window.addEventListener('load', init);
diff --git a/demos/blockfactory_old/icon.png b/demos/blockfactory_old/icon.png
deleted file mode 100644
index 4f8b72f41ed08a623502a97f09813f5dff5c9069..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 3634
zcmZ`+XE@wJxBstI)+)PtiMD#8tzM$6x|<+TgJ`RZZgoL+5d;Y$f(StbA*)1-AVsvS
z8bS2ldr2fD;@#Z)ez_m+Gvz$z%$zf4=FI$N=AOwlGy{|y3IG7Z6^x!4nYxmT351$l
z@6itJkqM=Twy`z<)TYt>aiSvg0xlRcV*rR01ps_J0GyIV_;mmXK?1-g768<80DvRl
z>048Eas#!qAzBYO|Cb8e$}-6k+5n7A5CG7z{3{^f$x}|UkS6$wu|CZV9f<7$Z*-^P
z8QB}e6+LZ>@X4M0Eh`oezHWYv-`V>*v!_?58`gZnUQE>yStZ8I3hiI@crg)+AC+Ps
zrQm2Er3jc%q##D5lf|gwwuEBFjW{aExg&MI;SlE
zDPH~AiG6`bf=v8=UYIX1?R9v(_h#mH;x}&ZsChnzcTsos37)w7h5FPE{wVj&gf*0f
z$4!%Gfp`>|BO;|52H5jPX$#WFDG^@k;Al01&Bdy%7or25Zf4e0e
zXojB#jY`1}W-no2qYGvV4X9z=5OLb-MVe`CF7369I&>@c&Q+|EoQt)z3H7NrZJs)sH-PxYU}l_=(lZZJ6#GAzr5#Cit!~
z0V&RQ@{wb^ThkbQ3_B6{>8Alm$rUd6r0if%7QCigegM~ZuWKJK3Xm
zLTo}G67g_~xd7Qzjy~xaQ<1+%>mtaX%+>y1!_Z%XV>M(Z<1Ye2mLxV<>Z}~tXeMEs
z0etVzJd!kA0-zk)BB_ksB+TLtP4=@`P5JrI5vL;Q1*dbU6#8;QPh4aM9Sv(?d_R1z
zp{!t%a#D)kTmHUA{9PuCB)-r)onIo?P^G}Uh-ji1Qd6C9u<$!vy_4ce9$wBOp?LjT
z9|b4Tj7X7ggZ1M%RE7whOVGCe5k!V=F_uZH=Y%IceRDMa^V=R)5r(&83X5guGT{oE
zSW5B6-S;DypuyUZj|u&?NVRP%RlNlY0r@7x!glu3X^$k{Cg<843cd%_!~ZY<`1}?8PiR##bb)u()A?9=rLov&U$}#;OyST7{kAcN+J;j(3{YzX6oW%
zI0^ze(0}*pS&&hFl_Le5%jlCIUN{FS`^h-4&ZWs@D_~|zs5&3{6Jo`(jan7zaE<8V
z;jf!4BsLCf4x;Un0s;k&e2EGbewO&5b}U?Hp2MMbKF3xd=(=oqT{~_?RMIK|sn$h5
zoYJDqWx^wHDfP9BMocHATLS1Yp^W|!!H@MWscA-HE?%~H^7{11rxQyMA!&{rMv!xp6ogw=C;w(K3QeiB-orgs)K!LSw
zZ*HeFXY%5c6x9$-p$2D`YraZ&I+D;JdmjSP~O6uv9#2{urtQ`SZNOo;}J6l{2jscR&bCea+2Z
zSC;knDEV9R+XSwt#`15X7mzASy@ZYu8Em90WRi!4x;sZ2YgQIP+_5aY^U&Bnd(@k{
z1i38e97@X$L>#ZfrcGe;2W@2ls)s+VtY~0pidsU;I+pXCGX6v#1&@SMIU1V0&Tip*
z(d!>1H-FYMSIgEemtrXJKafJNi8CGI*U4cPE~JcrWoggJe=^Sa+?GTAl~Q5Hu|-_9RfBQR!`)XtBxr|WMC{hTIeRoZ`-MIz
z3Vu<~VyR_k;EWP|cV@_Xe6GQL8Yu*%mr8=C?oNmq2bX^Aq@P)=&lxn#eKXl<4syFj
z$Bm=+#Dr?T%oHv!>Kj7E=)BwYw#jWt!J36QmE5Mke~Z{CcDJHVs%J{VAk%KGrqAJP
zvJjzG{HkPU+&!8anfEewJ8i+%8!;Ol6M`;B0;FTBBZ+rKay{>?$e=KkGnSm}@opBt>aVu?Gjp`IoG
z&>`4z&bn}*!hthp1GQ4ys}A{l4*`q+)CzY*#$YIdJQ6l)`HuezzAXr~sHIA`&~#Cq-XFyLJI%bjr*f4L
zPM5yKpHFFDv_J@Ff!h-cXxhOGgnd-Fz`t7(7XM#%mNWfLT4uPwEamcdMyAc)3lRdd
z6v97y~%7yW1Ao`-Q`1`5pVh{^}3R;ncRMv$eJL)XmI&j|5ZU>PTO
z1dWM)&CWh!%twj@U!<*$l{0m@!+V~}=sWCZZJQ9#ayvEbk!{*HeDWvPa+Mo{Czokw
zO?SIaL4i?TkIPao@1Af2enIsOs23T!S=ccWM;5
z{NG-y_j?=nAl=H^3*&V{<5lSv>sG(qTA)kX5bZwIIxBRt{Bxx=-j&DTj;!ZAAtU_p
zzv8?cwzGE=P5l5VMO5{A2yU99?NLHSYC^&5%etI(E?hx*K2@1|+3kQO7eiKkzq^=9
zwWi|HQMAQ{dYpXZ2wgwJ=?Ht-?`#R(E8U0xYnqVDkd*gmSgab$yS6>K&JTmd%!
z$ZPHf3yL7Kg>lDc7hAb+*vpUv!K%NNe^EX+>e8_=AHN1^+=g_C>eLV@k*i)A4`L*J
zliZDizE@IqSkE>@p_@l(7U{GfQo0GYB%5~@vYyp!O7`q~&=15kxN{HK**A(IJvJnH1}V&fA~`72V?A
zi-@zIOh7w+1@NYvmP81|&(~Rx#_qY8hU%IRC5$m=nr}cShh|mQnc(F0yTO4e>@DUq
zAE&FV;#X`BhdlI5Ov=F0A@);>hs0!DdGCt<4tWXn5u^O_k~(iGy{3brvI-pAPkABA
z`OEJ9<#qG4ngaW_M#upJ)6`K5o{;nL$?`~zvT1GWorSRB?>bBirS}>9QI|EA5pWoJ
zJT0BZV(Upls6I_)scVY
z#YVwi@(fY$!H=yn^=t$=6{l&S5g$d5scSaX;5)iCfwqEg?Me$Z#h>SYP#LE@Vm5o+
z{Q&1}s_SJp1{ZZ?(SgI6Q&@{QvnFiD6<{SDs_D`d9MM&H6@#y_LE=8SY3x&~ZJ3EJ
zJ$5NK!Na_g&L~@lxJBv&2?4&#BqA-G^a4VW=EP-G*0z))IWtsQx>ewz}+3aL3P8
zI>P!zbGu$wWvP;+*pdv+dN-5A7ov6gq?twc
zW(8PkYk%Kw4;=&lNX^U`$>6?8etk=ju>_jFqC$#DnS8-|=#o43OCHoF`plH~+xpo9
z+-&g^jN>ss6%V2Dq+dVLirnwTrItbofAsH54t3@+yj_RVWVBg)#(8+1K7uF%j}E--
znE`q+XBp8TG&Lnn-oU%(KM~xoLom~MtD}BCd$Gc~a{K4`xpTkzXO8cvKqYjJvUKyv
z|0j`Ped}OXr(icV7n~cJ0CGrK6)B{ul$^YUtg4!vlA3~&1QMx+L}Jb-&i{{sufOXp
Y_lW;rLABUPm#hF>(Z8lwrGt(A5AAP%jQ{`u

diff --git a/demos/blockfactory_old/index.html b/demos/blockfactory_old/index.html
deleted file mode 100644
index 2509e7138..000000000
--- a/demos/blockfactory_old/index.html
+++ /dev/null
@@ -1,224 +0,0 @@
-
-
-
-  
-  
-  Blockly Demo: Block Factory
-  
-  
-  
-  
-  
-  
-  
-
-
-  
-    
-      
-      
-    
-    
-      
-      
-    
-  
-

Blockly > - Demos > Block Factory

-
- - - - - -
-

Preview: - -

-
- - - -
-
-
-
-
- - - - - - - - - - - - - - - - -
-
-
-

Language code: - -

-
-

-              
-            
-

Generator stub: - -

-
-

-            
-
- - - diff --git a/demos/blockfactory_old/link.png b/demos/blockfactory_old/link.png deleted file mode 100644 index 11dfd82845e582b4272969694e305b71226546ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 228 zcmeAS@N?(olHy`uVBq!ia0vp^q9Dw{1|(OCFP#RY*pj^6T^Rm@;DWu&Cj&(|3p^r= z85p>QL70(Y)*K0-AbW|YuPggKc0N&VvE7#n?*fGiJzX3_EKVmUNU$z$NLw(!vGJk3 zRNeUl2Lu|Hs4-}@ty?!gf_WluTjTeND+hVgS}y%xxJ8kf`7o=-?Z$@ld=8n+Y;0|x zGoBsf{oB9nr9ESh?^-5iAmE<0gmIF$Y-bNQv&uK-S*@DB$@L;VOj?W#_t@Nv|4M%| Q16t1D>FVdQ&MBb@0ONT`aR2}S From 8fc439f090c8e19b95ecd0185550fb5892b1b2ed Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Fri, 15 Mar 2024 18:36:10 +0000 Subject: [PATCH 25/92] feat: add headless workspace comment class (#7916) * feat: add empty definitions for comment class * feat: implement constructor * feat: add method implementations * feat: add own properties * chore: add tsdoc * fix: typos * chore: cleanup TODOs --- core/comments.ts | 1 + core/comments/workspace_comment.ts | 168 +++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+) create mode 100644 core/comments/workspace_comment.ts diff --git a/core/comments.ts b/core/comments.ts index d7a9aed3a..9487d45f2 100644 --- a/core/comments.ts +++ b/core/comments.ts @@ -5,3 +5,4 @@ */ export {CommentView} from './comments/comment_view.js'; +export {WorkspaceComment} from './comments/workspace_comment.js'; diff --git a/core/comments/workspace_comment.ts b/core/comments/workspace_comment.ts new file mode 100644 index 000000000..4a563c544 --- /dev/null +++ b/core/comments/workspace_comment.ts @@ -0,0 +1,168 @@ +/** + * @license + * Copyright 2024 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {Workspace} from '../workspace.js'; +import {Size} from '../utils/size.js'; +import {Coordinate} from '../utils/coordinate.js'; +import * as idGenerator from '../utils/idgenerator.js'; + +export class WorkspaceComment { + /** The unique identifier for this comment. */ + public readonly id: string; + + /** The text of the comment. */ + private text = ''; + + /** The size of the comment in workspace units. */ + private size = new Size(120, 100); + + /** Whether the comment is collapsed or not. */ + private collapsed = false; + + /** Whether the comment is editable or not. */ + private editable = true; + + /** Whether the comment is movable or not. */ + private movable = true; + + /** Whether the comment is deletable or not. */ + private deletable = true; + + /** The location of the comment in workspace coordinates. */ + private location = new Coordinate(0, 0); + + /** Whether this comment has been disposed or not. */ + private disposed = false; + + /** + * Constructs the comment. + * + * @param workspace The workspace to construct the comment in. + * @param id An optional ID to give to the comment. If not provided, one will + * be generated. + */ + constructor( + protected readonly workspace: Workspace, + id?: string, + ) { + this.id = id && !workspace.getCommentById(id) ? id : idGenerator.genUid(); + + // TODO(7909): Fire events. + } + + /** Sets the text of the comment. */ + setText(text: string) { + this.text = text; + } + + /** Returns the text of the comment. */ + getText(): string { + return this.text; + } + + /** Sets the comment's size in workspace units. */ + setSize(size: Size) { + this.size = size; + } + + /** Returns the comment's size in workspace units. */ + getSize(): Size { + return this.size; + } + + /** Sets whether the comment is collapsed or not. */ + setCollapsed(collapsed: boolean) { + this.collapsed = collapsed; + } + + /** Returns whether the comment is collapsed or not. */ + isCollapsed(): boolean { + return this.collapsed; + } + + /** Sets whether the comment is editable or not. */ + setEditable(editable: boolean) { + this.editable = editable; + } + + /** + * Returns whether the comment is editable or not, respecting whether the + * workspace is read-only. + */ + isEditable(): boolean { + return this.isOwnEditable() && !this.workspace.options.readOnly; + } + + /** + * Returns whether the comment is editable or not, only examining its own + * state and ignoring the state of the workspace. + */ + isOwnEditable(): boolean { + return this.editable; + } + + /** Sets whether the comment is movable or not. */ + setMovable(movable: boolean) { + this.movable = movable; + } + + /** + * Returns whether the comment is movable or not, respecting whether the + * workspace is read-only. + */ + isMovable() { + return this.isOwnMovable() && !this.workspace.options.readOnly; + } + + /** + * Returns whether the comment is movable or not, only examining its own + * state and ignoring the state of the workspace. + */ + isOwnMovable() { + return this.movable; + } + + /** Sets whether the comment is deletable or not. */ + setDeletable(deletable: boolean) { + this.deletable = deletable; + } + + /** + * Returns whether the comment is deletable or not, respecting whether the + * workspace is read-only. + */ + isDeletable(): boolean { + return this.isOwnDeletable() && !this.workspace.options.readOnly; + } + + /** + * Returns whether the comment is deletable or not, only examining its own + * state and ignoring the state of the workspace. + */ + isOwnDeletable(): boolean { + return this.deletable; + } + + /** Moves the comment to the given location in workspace coordinates. */ + moveTo(location: Coordinate) { + this.location = location; + } + + /** Returns the position of the comment in workspace coordinates. */ + getRelativeToSurfaceXY(): Coordinate { + return this.location; + } + + /** Disposes of this comment. */ + dispose() { + this.disposed = true; + } + + /** Returns whether the comment has been disposed or not. */ + isDisposed() { + return this.disposed; + } +} From c97b13632c715dd6ac8f0cf59991571781206cf8 Mon Sep 17 00:00:00 2001 From: Christopher Allen Date: Fri, 15 Mar 2024 23:09:41 +0100 Subject: [PATCH 26/92] feat(build)!: Introduce `exports` section in `package.json` (#7822) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(typings): Remove bogus .d.ts files; add new languages PR #3821 added .d.ts files for every file in msg/json/, but several of these are internal utility files rather than translations, and do not result in a langfile being output by create_messages.py when building langfiles. In the meantime we have added a few new languages that are being published but which have (until now) not had the corresponding type declarations. * feat(build)!: Add exports section to package.json Add an exports stanza to package.json, enumerating existing entrypoints in a new format. - The original main entrypoint, index.js, is removed since the exports section can point directly at node.js or browser.js. - No change made (yet) to other entrypoints (core, blocks, generators); these will be dealt with in a subsequent PR. - The msg/en entrypoint is included in the top-level package.json as an example; entries for all other languages created as part of the packageJSON package task. BREAKING CHANGE: The introduction of an exports stanza means that correctly-behaved tools (node.js, bundlers like webpack, etc.) will only allow importing of the specified entrypoints. Here is the full list of permitted entrypoints that can be imported or required: - blockly - blockly/core - blockly/blocks - blockly/dart - blockly/lua - blockly/javascript - blockly/php - blockly/python - blockly/msg/, for all supported language codes (e.g blockly/msg/en, blockly/msg/fr, blockly/msg/de, etc.) If you previously impored any other paths from the blockly package you will need to update your imports. Here are the most common paths that may have been used, and their correct replacements: | If you previously imported: | Import instead: | | -------------------------------- | -------------------------- | | blockly/index.js | blockly | | blockly/node.js | blockly | | blockly/browser.js | blockly | | blockly/blockly.min | This file should only be loaded as a