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,226 @@
const fs = require('fs');
const path = require('path');
const {test} = require('tap');
const VM = require('../../src/virtual-machine');
const BlockType = require('../../src/extension-support/block-type');
const ArgumentType = require('../../src/extension-support/argument-type');
const compilerAndInterpreter = (name, callback) => {
test(`${name} - interpreted`, t => {
callback(t, {
enabled: false
});
});
test(`${name} - compiled`, t => {
callback(t, {
enabled: true
});
});
};
const fixture = fs.readFileSync(path.join(__dirname, '..', 'fixtures', 'tw-hats-and-events.sb3'));
compilerAndInterpreter('hats and events', (t, co) => {
const vm = new VM();
vm.setCompilerOptions(co);
let log = [];
let hatReturns = false;
class TestExtension {
getInfo () {
return {
id: 'testpredicate',
name: 'Test Predicate',
blocks: [
{
opcode: 'event',
blockType: BlockType.EVENT,
text: 'event block',
isEdgeActivated: false
},
{
opcode: 'hat',
blockType: BlockType.HAT,
text: 'hat block',
isEdgeActivated: false
},
{
opcode: 'complexhat',
blockType: BlockType.HAT,
text: 'complex hat [MENU] if [INPUT]',
isEdgeActivated: false,
arguments: {
MENU: {
menu: 'test'
},
INPUT: {
type: ArgumentType.STRING,
defaultValue: 'default'
}
}
},
{
opcode: 'clickme',
blockType: BlockType.HAT,
text: 'stack click test [INPUT]',
isEdgeActivated: false,
arguments: {
INPUT: {
type: ArgumentType.STRING,
defaultValue: 'default'
}
}
}
],
menus: {
test: {
acceptReporters: false,
items: ['a', 'b', 'c']
}
}
};
}
event () {
log.push('this should never run');
}
hat () {
log.push(`hat ${hatReturns}`);
return hatReturns;
}
complexhat ({INPUT}) {
log.push(`complex hat ${INPUT}`);
return !!INPUT;
}
clickme () {
log.push('clickme');
return Promise.resolve(false);
}
}
vm.extensionManager.addBuiltinExtension('testpredicate', TestExtension);
vm.on('COMPILE_ERROR', () => {
t.fail('Compile error');
});
vm.loadProject(fixture).then(async () => {
log = vm.runtime.getTargetForStage().lookupVariableByNameAndType('log', 'list').value;
t.same(log, [], 'sanity check - log starts empty');
// Let it run for a bit. Nothing should happen on its own.
for (let i = 0; i < 5; i++) {
vm.runtime._step();
}
t.same(log, [], 'nothing happens initially');
// See if events work
vm.runtime.startHats('testpredicate_event');
t.same(log, [], 'event function does not get called, even if it exists');
vm.runtime._step();
t.same(log, ['event'], 'ran event script');
log.length = 0;
// Test hat that returns false
hatReturns = false;
vm.runtime.startHats('testpredicate_hat');
t.same(log, ['hat false'], 'ran hat function');
vm.runtime._step();
t.same(log, ['hat false'], 'did not run hat script');
// Test hat that returns true
hatReturns = true;
vm.runtime.startHats('testpredicate_hat');
t.same(log, [
'hat false',
'hat true'
], 'ran hat function');
vm.runtime._step();
t.same(log, [
'hat false',
'hat true',
'hat'
], 'ran hat script');
log.length = 0;
// Test hat that returns false in a Promise
hatReturns = Promise.resolve(false);
vm.runtime.startHats('testpredicate_hat');
t.same(log, ['hat [object Promise]'], 'ran hat function');
vm.runtime._step();
t.same(log, ['hat [object Promise]'], 'hat script does not run before promise finishes');
await Promise.resolve(); // Allow promise to be processed
vm.runtime._step();
t.same(log, ['hat [object Promise]'], 'hat script still does not run');
log.length = 0;
// Test hat that returns true in a Promise
hatReturns = Promise.resolve(true);
vm.runtime.startHats('testpredicate_hat');
t.same(log, ['hat [object Promise]'], 'ran hat function');
vm.runtime._step();
t.same(log, ['hat [object Promise]'], 'hat script does not run before promise finishes');
await Promise.resolve();
vm.runtime._step();
t.same(log, ['hat [object Promise]', 'hat'], 'hat script runs after promise finishes');
log.length = 0;
// Test complex hat
vm.runtime.startHats('testpredicate_complexhat', {
MENU: 'a'
});
t.same(log, [
'complex hat ',
'complex hat 1'
], 'ran complex hat functions');
vm.runtime._step();
t.same(log, [
'complex hat ',
'complex hat 1',
'complex hat a 2'
], 'ran complex hat script');
log.length = 0;
// Test complex hat with a complex input
vm.runtime.startHats('testpredicate_complexhat', {
MENU: 'b'
});
t.same(log, [], 'control flow in complex inputs is not run immediately');
vm.runtime._step();
t.same(log, [
'evaluated block ',
'evaluated block 1'
], 'evaluated complex inputs but not hat function');
vm.runtime._step();
t.same(log, [
'evaluated block ',
'evaluated block 1',
'complex hat ',
'complex hat 1',
'complex hat b 2'
], 'evaluated complex hat functions and scripts');
log.length = 0;
// Test that in stackClick mode, the hat block still gets run, but the result is ignored
const sprite = vm.runtime.targets[1];
const allBlocks = Object.values(sprite.sprite.blocks._blocks);
const clickBlockId = allBlocks.find(i => i.opcode === 'testpredicate_clickme').id;
vm.runtime._pushThread(clickBlockId, sprite, {
stackClick: true
});
t.same(log, [], 'stackClick does not run anything immediately');
vm.runtime._step();
t.same(log, ['evaluated block something'], 'stackClick hat ran input');
vm.runtime._step();
t.same(log, ['evaluated block something', 'clickme'], 'stackClick hat input evaluated');
await Promise.resolve(); // Allow promise to be processed
vm.runtime._step();
t.same(log, ['evaluated block something', 'clickme', 'stack click'], 'stackClick hat script ran');
t.end();
});
});