diff --git a/test/fixtures/tw-hats-and-events.sb3 b/test/fixtures/tw-hats-and-events.sb3 new file mode 100644 index 00000000000..ad8c5ad7dc1 Binary files /dev/null and b/test/fixtures/tw-hats-and-events.sb3 differ diff --git a/test/integration/tw_hats_and_events.js b/test/integration/tw_hats_and_events.js new file mode 100644 index 00000000000..98031c22595 --- /dev/null +++ b/test/integration/tw_hats_and_events.js @@ -0,0 +1,192 @@ +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' + } + } + } + ], + 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; + } + } + 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'); + + t.end(); + }); +});