Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
679 changes: 485 additions & 194 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 1 addition & 6 deletions packages/blockly/gulpfile.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,7 @@ import {
pack,
typings,
} from './scripts/gulpfiles/package_tasks.mjs';
import {
generators,
interactiveMocha,
test,
} from './scripts/gulpfiles/test_tasks.mjs';
import {generators, test} from './scripts/gulpfiles/test_tasks.mjs';

const clean = parallel(cleanBuildDir, cleanReleaseDir);

Expand Down Expand Up @@ -75,7 +71,6 @@ export {
clean,
test,
generators as testGenerators,
interactiveMocha,
buildAdvancedCompilationTest,
docs,
typings,
Expand Down
21 changes: 15 additions & 6 deletions packages/blockly/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,22 @@
},
"imports": {
"#core/*": "./build/src/core/*",
"#blocks/*": "./build/src/blocks/*",
"#generators/*": "./build/src/generators/*"
},
"publishConfig": {
"directory": "dist"
},
"nx": {
"implicitDependencies": ["!@blockly/block-test"],
"implicitDependencies": [
"!@blockly/block-test"
],
"targets": {
"build": {
"dependsOn": ["package", "^build"]
"dependsOn": [
"package",
"^build"
]
},
"test": {
"dependsOn": [
Expand All @@ -42,10 +48,13 @@
"projects": "@blockly/dev-tools",
"target": "build"
}
]
]
},
"docs": {
"dependsOn": ["package", "^build"]
"dependsOn": [
"package",
"^build"
]
}
}
},
Expand Down Expand Up @@ -73,7 +82,7 @@
"test:browser": "npx mocha --config tests/browser/.mocharc.js",
"test:mocha:node": "npx mocha --config tests/mocha/.mocharc.node.cjs \"tests/mocha/**/*_test.js\"",
"test:generators": "gulp testGenerators",
"test:mocha:interactive": "npm run build && concurrently -n tsc,server \"tsc --watch --preserveWatchOutput --outDir \"build/src\" --declarationDir \"build/declarations\"\" \"gulp interactiveMocha\"",
"test:mocha:interactive": "npm run build && node scripts/prepare_mocha_bundle.mjs && concurrently -n tsc,esbuild,python3 \"tsc --watch --preserveWatchOutput --outDir \"build/src\" --declarationDir \"build/declarations\"\" \"esbuild build/tests/bundle-entry.js --bundle --sourcemap --alias:blockly/core=./build/src/core/blockly.js --alias:blockly/blocks=./build/src/blocks/blocks.js --alias:blockly=./build/src/core/blockly.js --outfile=build/tests/mocha-bundle.js --servedir=. --serve=127.0.0.1:8080 --watch=forever --log-level=warning\" \"python3 -m webbrowser 'http://localhost:8080/tests/mocha/index.html'\"",
"test:compile:advanced": "gulp buildAdvancedCompilationTest --debug",
"updateGithubPages": "npm ci && gulp updateGithubPages --upstream",
"updateGithubPages:staging": "npm ci && gulp updateGithubPages --use-local"
Expand Down Expand Up @@ -138,12 +147,12 @@
"async-done": "^2.0.0",
"chai": "^6.2.2",
"concurrently": "^10.0.4",
"esbuild": "^0.28.2",
"glob": "^13.0.6",
"globals": "^17.7.0",
"google-closure-compiler": "^20260803.0.0",
"gulp": "^5.0.1",
"gulp-concat": "^2.6.1",
"gulp-gzip": "^1.4.2",
"gulp-header": "^2.0.12",
"gulp-rename": "^2.1.0",
"gulp-replace": "^1.1.4",
Expand Down
144 changes: 3 additions & 141 deletions packages/blockly/scripts/gulpfiles/test_tasks.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,11 @@
import asyncDone from 'async-done';
import {spawnSync} from 'child_process';
import * as fs from 'fs';
import {globSync} from 'glob';
import * as gulp from 'gulp';
import gzip from 'gulp-gzip';
import * as path from 'path';
import {rimraf} from 'rimraf';

import {RELEASE_DIR, TEST_TSC_OUTPUT_DIR} from './config.mjs';

import {runMochaTestsInBrowser} from '../../tests/mocha/webdriver.js';
import {TEST_TSC_OUTPUT_DIR} from './config.mjs';

const OUTPUT_DIR = 'build/generators';
const GOLDEN_DIR = 'tests/generators/golden';
Expand Down Expand Up @@ -168,146 +164,13 @@ function renamings() {
}

/**
* Helper method for gzipping file.
* @param {string} file Target file.
* @return {Promise} Asynchronous result.
*/
function gzipFile(file) {
return new Promise((resolve) => {
const name = path.posix.join(RELEASE_DIR, file);

const stream = gulp.src(name)
.pipe(gzip())
.pipe(gulp.dest(RELEASE_DIR));

stream.on('end', () => {
resolve();
});
});
}

/**
* Helper method for comparing file size.
* @param {string} file Target file.
* @param {number} expected Expected size.
* @return {number} 0: success / 1: failed.
*/
function compareSize(file, expected) {
const name = path.posix.join(RELEASE_DIR, file);
const compare = Math.floor(expected * 1.1);
const stat = fs.statSync(name);
const size = stat.size;

if (!compare) {
const message = `Failed: Previous size of ${name} is undefined.`;
console.log(`${BOLD_RED}${message}${ANSI_RESET}`);
return 1;
}

if (size > compare) {
const message = `Failed: ` +
`Size of ${name} has grown more than 10%. ${size} vs ${expected}`;
console.log(`${BOLD_RED}${message}${ANSI_RESET}`);
return 1;
}

const message =
`Size of ${name} at ${size} compared to previous ${expected}`;
console.log(`${BOLD_GREEN}${message}${ANSI_RESET}`);
return 0;
}

/**
* Helper method for zipping the compressed files.
* @return {Promise} Asynchronous result.
*/
function zippingFiles() {
// GZip them for additional size comparisons (keep originals, force
// overwite previously-gzipped copies).
console.log('Zipping the compressed files');
const gzip1 = gzipFile('blockly_compressed.js');
const gzip2 = gzipFile('blocks_compressed.js');
return Promise.all([gzip1, gzip2]);
}

/**
* Check the sizes of built files for unexpected growth.
* @return {Promise} Asynchronous result.
*/
async function metadata() {
return runTestTask('metadata', async () => {
// Zipping the compressed files.
await zippingFiles();
// Read expected size from script.
const contents = fs.readFileSync('tests/scripts/check_metadata.sh')
.toString();
const pattern = /^readonly (?<key>[A-Z_]+)=(?<value>\d+)$/gm;
const matches = contents.matchAll(pattern);
const expected = {};
for (const match of matches) {
expected[match.groups.key] = match.groups.value;
}

// Check the sizes of the files.
let failed = 0;
failed += compareSize('blockly_compressed.js',
expected.BLOCKLY_SIZE_EXPECTED);
failed += compareSize('blocks_compressed.js',
expected.BLOCKS_SIZE_EXPECTED);
failed += compareSize('blockly_compressed.js.gz',
expected.BLOCKLY_GZ_SIZE_EXPECTED);
failed += compareSize('blocks_compressed.js.gz',
expected.BLOCKS_GZ_SIZE_EXPECTED);
if (failed > 0) {
throw new Error('Unexpected growth was detected.');
}
});
}

/**
* Generates tests/mocha/test-modules.generated.mjs,
* the list of tests imported by the browser harness (tests/mocha/index.html).
* Keeping it generated from a glob means new *_test.js files are picked up
* automatically and the browser and Node runners stay in sync.
* @return {Promise} Asynchronous result.
*/
function generateMochaIndex() {
return runTestTask('generateMochaIndex', async () => {
const files = globSync('**/*_test.js', {cwd: 'tests/mocha'}).sort();

const body = files.map((f) => `import './${f}';`).join('\n') + '\n';
fs.writeFileSync('tests/mocha/test-modules.generated.mjs', body);
});
}

/**
* Run Mocha tests inside a browser.
* Run Mocha tests under Node.
* @return {Promise} Asynchronous result.
*/
function mocha() {
// Run in a subprocess so webdriverio is not loaded inside gulp's asyncDone
// domain (which has been observed to exit the process on CI after ~2s).
return runTestCommand('mocha', 'node tests/mocha/webdriver.js');
return runTestCommand('mocha', 'npm run test:mocha:node');
}

/**
* Run Mocha tests inside a browser and keep the browser open upon completion.
* @return {Promise} Asynchronous result.
*/
export const interactiveMocha = gulp.series(
generateMochaIndex,
function interactiveMochaRun() {
return runTestTask('interactiveMocha', () => {
return runMochaTestsInBrowser(false).then((result) => {
if (result) {
throw new Error('Mocha tests failed');
}
console.log('Mocha tests passed');
});
});
},
);

/**
* Helper method for comparison file.
* @param {string} file1 First target file.
Expand Down Expand Up @@ -429,7 +292,6 @@ const tasks = [
// Build must run before the remaining tasks
build,
renamings,
generateMochaIndex,
mocha,
generators,
typeDefinitions,
Expand Down
43 changes: 43 additions & 0 deletions packages/blockly/scripts/prepare_mocha_bundle.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* @license
* Copyright 2026 Raspberry Pi Foundation
* SPDX-License-Identifier: Apache-2.0
*/

import {globSync} from 'glob';
import * as fs from 'node:fs/promises';
import {createRequire} from 'node:module';
import * as path from 'node:path';

const require = createRequire(import.meta.url);

const TEST_DIR = 'tests/mocha';
const OUT_DIR = 'build/tests';
const ENTRY_POINT = path.posix.join(OUT_DIR, 'bundle-entry.js');

/**
* Writes out a file that loads the in-browser Mocha test bootstrap code and
* the tests themselves, and acts as an entrypoint for esbuild to enumerate and
* bundle the various scripts that make up the test suite.
*/
async function writeEntryPoint() {
const tests = globSync('**/*_test.{js,ts}', {cwd: TEST_DIR}).sort();
const dir = path.posix.relative(path.posix.dirname(ENTRY_POINT), TEST_DIR);

await fs.writeFile(
ENTRY_POINT,
[
`import '${dir}/browser-setup.js';`,
...tests.map((file) => `import '${dir}/${file}';`),
].join('\n'),
);
}

await fs.mkdir(OUT_DIR, {recursive: true});
// Resolve and copy Mocha's prebuilt browser distribution.
await fs.copyFile(
require.resolve('mocha/mocha.js'),
path.join(OUT_DIR, 'mocha.js'),
);
// Write out the esbuild entrypoint that imports the test files.
await writeEntryPoint();
6 changes: 0 additions & 6 deletions packages/blockly/tests/mocha/.mocharc.js

This file was deleted.

3 changes: 2 additions & 1 deletion packages/blockly/tests/mocha/.mocharc.node.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@

module.exports = {
ui: 'tdd',
reporter: 'spec',
'node-option': ['enable-source-maps'],
reporter: './tests/mocha/quiet_reporter.cjs',
parallel: true,
require: ['./tests/mocha/node-setup.mjs'],
timeout: 10000,
Expand Down
40 changes: 40 additions & 0 deletions packages/blockly/tests/mocha/browser-setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* @license
* Copyright 2026 Raspberry Pi Foundation
* SPDX-License-Identifier: Apache-2.0
*/

/**
* @fileoverview Browser harness for the Blockly unit suite.
*
* This is the browser counterpart to node-setup.mjs: it loads Blockly, exposes
* the globals the tests expect, installs the shared DOM fixtures and configures
* Mocha. It is bundled, along with every test module, into
* build/tests/mocha-bundle.js, which is loaded by the test page.
*/

// Prevent Prettier from reordering imports to load blocks before Blockly.
// organize-imports-ignore
import * as Blockly from '#core/blockly.js';
import '#blocks/blocks.js';
import {javascriptGenerator} from '#generators/javascript.js';
import {config as chaiConfig} from 'chai';
import sinon from 'sinon';
import 'mocha/mocha.css';
import {installFixtures} from './test_helpers/dom_fixtures.js';
import '@blockly/block-test';

chaiConfig.showDiff = false;

globalThis.Blockly = Blockly;
globalThis.javascriptGenerator = javascriptGenerator;
globalThis.sinon = sinon;

// The focusable trees, toolbox definitions and #blocklyDiv the tests expect.
// Shared with the Node harness so both run against identical markup.
installFixtures();

mocha.setup({
ui: 'tdd',
failZero: true,
});
6 changes: 2 additions & 4 deletions packages/blockly/tests/mocha/dropdowndiv_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,10 +177,8 @@ suite('DropDownDiv', function () {
const block = this.setUpBlockWithField();
const field = Array.from(block.getFields())[0];

const errorMsgRegex = /Cannot read properties of null.+?/;
assert.throws(
() => Blockly.DropDownDiv.show(field, false, 50, 60, 70, 80, false),
errorMsgRegex,
assert.throws(() =>
Blockly.DropDownDiv.show(field, false, 50, 60, 70, 80, false),
);
});

Expand Down
Loading
Loading