Initial commit of 001code-html Scratch frontend project.

Includes scratch-gui, scratch-vm, scratch-blocks, scratch-render, scratch-l10n, and deployment config.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-16 15:37:45 +08:00
commit 6e0a1fbcbb
11350 changed files with 965674 additions and 0 deletions

View File

@@ -0,0 +1,198 @@
const {test} = require('tap');
const fs = require('fs');
const path = require('path');
const VM = require('../../src/virtual-machine');
const Thread = require('../../src/engine/thread');
const Clone = require('../../src/util/clone');
const fixturePath = path.join(__dirname, '../fixtures/tw-block-stop-thread.sb3');
const fixtureData = fs.readFileSync(fixturePath);
const stopByRetireThread = thread => {
thread.target.runtime.sequencer.retireThread(thread);
};
const stopBySetStatus = thread => {
thread.status = Thread.STATUS_DONE;
};
test('constants', t => {
t.equal(Thread.STATUS_DONE, 4);
t.end();
});
for (const [enableCompiler, stopFunction] of [
[true, stopByRetireThread],
[true, stopBySetStatus],
[false, stopByRetireThread],
[false, stopBySetStatus]
]) {
const subtestName = `${enableCompiler ? 'compiler' : 'interpreter'} - ${stopFunction.name}`;
test(`${subtestName} - stop in command block`, t => {
const vm = new VM();
vm.setCompilerOptions({enabled: enableCompiler});
t.equal(vm.runtime.compilerOptions.enabled, enableCompiler);
const callOrder = [];
vm.addAddonBlock({
procedureCode: 'first block %s',
arguments: ['number or text'],
callback: (args, util) => {
callOrder.push(['first block', Clone.simple(args)]);
stopFunction(util.thread);
}
});
vm.addAddonBlock({
procedureCode: 'inner block',
return: 1,
callback: args => {
callOrder.push(['input block', Clone.simple(args)]);
return callOrder.length;
}
});
vm.addAddonBlock({
procedureCode: 'second block',
callback: args => {
callOrder.push(['second block', Clone.simple(args)]);
}
});
vm.loadProject(fixtureData).then(() => {
vm.greenFlag();
vm.runtime._step();
t.same(callOrder, [
['input block', {}],
['first block', {'number or text': 1}]
]);
t.end();
});
});
test(`${subtestName} - stop in command block after yielding once`, t => {
const vm = new VM();
vm.setCompilerOptions({enabled: enableCompiler});
t.equal(vm.runtime.compilerOptions.enabled, enableCompiler);
const callOrder = [];
vm.addAddonBlock({
procedureCode: 'first block %s',
arguments: ['number or text'],
callback: (args, util) => {
callOrder.push(['first block', Clone.simple(args)]);
if (callOrder.length === 1) {
util.yield();
} else {
stopFunction(util.thread);
}
}
});
vm.addAddonBlock({
procedureCode: 'inner block',
return: 1,
// Ignore calls because interpreter will re-evaluate on yield but compiler won't
callback: () => 5
});
vm.addAddonBlock({
procedureCode: 'second block',
callback: args => {
callOrder.push(['second block', Clone.simple(args)]);
}
});
vm.loadProject(fixtureData).then(() => {
vm.greenFlag();
vm.runtime._step();
t.same(callOrder, [
['first block', {'number or text': 5}],
['first block', {'number or text': 5}]
]);
t.end();
});
});
test(`${subtestName} - stop in reporter block`, t => {
const vm = new VM();
vm.setCompilerOptions({enabled: enableCompiler});
t.equal(vm.runtime.compilerOptions.enabled, enableCompiler);
const callOrder = [];
vm.addAddonBlock({
procedureCode: 'first block %s',
arguments: ['number or text'],
callback: args => {
callOrder.push(['first block', Clone.simple(args)]);
}
});
vm.addAddonBlock({
procedureCode: 'inner block',
return: 1,
callback: (args, util) => {
callOrder.push(['input block', Clone.simple(args)]);
stopFunction(util.thread);
return callOrder.length;
}
});
vm.addAddonBlock({
procedureCode: 'second block',
callback: args => {
callOrder.push(['second block', Clone.simple(args)]);
}
});
vm.loadProject(fixtureData).then(() => {
vm.greenFlag();
vm.runtime._step();
t.same(callOrder, [
['input block', {}]
]);
t.end();
});
});
test(`${subtestName} - stop in reporter block after yielding once`, t => {
const vm = new VM();
vm.setCompilerOptions({enabled: enableCompiler});
t.equal(vm.runtime.compilerOptions.enabled, enableCompiler);
const callOrder = [];
vm.addAddonBlock({
procedureCode: 'first block %s',
arguments: ['number or text'],
callback: args => {
callOrder.push(['first block', Clone.simple(args)]);
}
});
vm.addAddonBlock({
procedureCode: 'inner block',
return: 1,
callback: (args, util) => {
callOrder.push(['input block', Clone.simple(args)]);
if (callOrder.length === 1) {
util.yield();
// TODO: interpreter bug, should not need to do this...
util.thread.peekStackFrame().waitingReporter = true;
} else {
stopFunction(util.thread);
}
return callOrder.length;
}
});
vm.addAddonBlock({
procedureCode: 'second block',
callback: args => {
callOrder.push(['second block', Clone.simple(args)]);
}
});
vm.loadProject(fixtureData).then(() => {
vm.greenFlag();
vm.runtime._step();
t.same(callOrder, [
['input block', {}],
['input block', {}]
]);
t.end();
});
});
}