Includes scratch-gui, scratch-vm, scratch-blocks, scratch-render, scratch-l10n, and deployment config. Co-authored-by: Cursor <cursoragent@cursor.com>
227 lines
7.6 KiB
JavaScript
227 lines
7.6 KiB
JavaScript
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();
|
|
});
|
|
});
|