Skip to content

Commit

Permalink
Add extensive event and hat test
Browse files Browse the repository at this point in the history
  • Loading branch information
GarboMuffin committed Aug 13, 2023
1 parent afb04f8 commit 186ad43
Show file tree
Hide file tree
Showing 2 changed files with 226 additions and 0 deletions.
Binary file added test/fixtures/tw-hats-and-events.sb3
Binary file not shown.
226 changes: 226 additions & 0 deletions test/integration/tw_hats_and_events.js
Original file line number Diff line number Diff line change
@@ -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();
});
});

0 comments on commit 186ad43

Please sign in to comment.