Spaces:
Runtime error
Runtime error
| const test = require('tap').test; | |
| const Sensing = require('../../src/blocks/scratch3_sensing'); | |
| const Runtime = require('../../src/engine/runtime'); | |
| const Sprite = require('../../src/sprites/sprite'); | |
| const RenderedTarget = require('../../src/sprites/rendered-target'); | |
| const BlockUtility = require('../../src/engine/block-utility'); | |
| test('getPrimitives', t => { | |
| const rt = new Runtime(); | |
| const s = new Sensing(rt); | |
| t.type(s.getPrimitives(), 'object'); | |
| t.end(); | |
| }); | |
| test('ask and answer with a hidden target', t => { | |
| const rt = new Runtime(); | |
| const s = new Sensing(rt); | |
| const util = {target: {visible: false}}; | |
| const expectedQuestion = 'a question'; | |
| const expectedAnswer = 'the answer'; | |
| // Test is written out of order because of promises, follow the (#) comments. | |
| rt.addListener('QUESTION', question => { | |
| // (2) Assert the question is correct, then emit the answer | |
| t.strictEqual(question, expectedQuestion); | |
| rt.emit('ANSWER', expectedAnswer); | |
| }); | |
| // (1) Emit the question. | |
| const promise = s.askAndWait({QUESTION: expectedQuestion}, util); | |
| // (3) Ask block resolves after the answer is emitted. | |
| promise.then(() => { | |
| t.strictEqual(s.getAnswer(), expectedAnswer); | |
| t.end(); | |
| }); | |
| }); | |
| test('ask and stop all dismisses question', t => { | |
| const rt = new Runtime(); | |
| const s = new Sensing(rt); | |
| const util = {target: {visible: false}}; | |
| const expectedQuestion = 'a question'; | |
| let call = 0; | |
| rt.addListener('QUESTION', question => { | |
| if (call === 0) { | |
| // (2) Assert the question was passed. | |
| t.strictEqual(question, expectedQuestion); | |
| } else if (call === 1) { | |
| // (4) Assert the question was dismissed. | |
| t.strictEqual(question, null); | |
| t.end(); | |
| } | |
| call += 1; | |
| }); | |
| // (1) Emit the question. | |
| s.askAndWait({QUESTION: expectedQuestion}, util); | |
| // (3) Emit the stop all event. | |
| rt.stopAll(); | |
| }); | |
| test('ask and stop other scripts dismisses if it is the last question', t => { | |
| const rt = new Runtime(); | |
| const s = new Sensing(rt); | |
| const util = {target: {visible: false, sprite: {}, getCustomState: () => ({})}, thread: {}}; | |
| const expectedQuestion = 'a question'; | |
| let call = 0; | |
| rt.addListener('QUESTION', question => { | |
| if (call === 0) { | |
| // (2) Assert the question was passed. | |
| t.strictEqual(question, expectedQuestion); | |
| } else if (call === 1) { | |
| // (4) Assert the question was dismissed. | |
| t.strictEqual(question, null); | |
| t.end(); | |
| } | |
| call += 1; | |
| }); | |
| // (1) Emit the questions. | |
| s.askAndWait({QUESTION: expectedQuestion}, util); | |
| // (3) Emit the stop for target event. | |
| rt.stopForTarget(util.target, util.thread); | |
| }); | |
| test('ask and stop other scripts asks next question', t => { | |
| const rt = new Runtime(); | |
| const s = new Sensing(rt); | |
| const util = {target: {visible: false, sprite: {}, getCustomState: () => ({})}, thread: {}}; | |
| const util2 = {target: {visible: false, sprite: {}, getCustomState: () => ({})}, thread: {}}; | |
| const expectedQuestion = 'a question'; | |
| const nextQuestion = 'a followup'; | |
| let call = 0; | |
| rt.addListener('QUESTION', question => { | |
| if (call === 0) { | |
| // (2) Assert the question was passed. | |
| t.strictEqual(question, expectedQuestion); | |
| } else if (call === 1) { | |
| // (4) Assert the next question was passed. | |
| t.strictEqual(question, nextQuestion); | |
| t.end(); | |
| } | |
| call += 1; | |
| }); | |
| // (1) Emit the questions. | |
| s.askAndWait({QUESTION: expectedQuestion}, util); | |
| s.askAndWait({QUESTION: nextQuestion}, util2); | |
| // (3) Emit the stop for target event. | |
| rt.stopForTarget(util.target, util.thread); | |
| }); | |
| test('ask and answer with a visible target', t => { | |
| const rt = new Runtime(); | |
| const s = new Sensing(rt); | |
| const util = {target: {visible: true}}; | |
| const expectedQuestion = 'a question'; | |
| const expectedAnswer = 'the answer'; | |
| rt.removeAllListeners('SAY'); // Prevent say blocks from executing | |
| rt.addListener('SAY', (target, type, question) => { | |
| // Should emit SAY with the question | |
| t.strictEqual(question, expectedQuestion); | |
| }); | |
| rt.addListener('QUESTION', question => { | |
| // Question should be blank for a visible target | |
| t.strictEqual(question, ''); | |
| // Remove the say listener and add a new one to assert bubble is cleared | |
| // by setting say to empty string after answer is received. | |
| rt.removeAllListeners('SAY'); | |
| rt.addListener('SAY', (target, type, text) => { | |
| t.strictEqual(text, ''); | |
| t.end(); | |
| }); | |
| rt.emit('ANSWER', expectedAnswer); | |
| }); | |
| s.askAndWait({QUESTION: expectedQuestion}, util); | |
| }); | |
| test('answer gets reset when runtime is disposed', t => { | |
| const rt = new Runtime(); | |
| const s = new Sensing(rt); | |
| const util = {target: {visible: false}}; | |
| const expectedAnswer = 'the answer'; | |
| rt.addListener('QUESTION', () => rt.emit('ANSWER', expectedAnswer)); | |
| const promise = s.askAndWait({QUESTION: ''}, util); | |
| promise.then(() => t.strictEqual(s.getAnswer(), expectedAnswer)) | |
| .then(() => rt.dispose()) | |
| .then(() => { | |
| t.strictEqual(s.getAnswer(), ''); | |
| t.end(); | |
| }); | |
| }); | |
| test('set drag mode', t => { | |
| const runtime = new Runtime(); | |
| runtime.requestTargetsUpdate = () => {}; // noop for testing | |
| const sensing = new Sensing(runtime); | |
| const s = new Sprite(null, runtime); | |
| const rt = new RenderedTarget(s, runtime); | |
| sensing.setDragMode({DRAG_MODE: 'not draggable'}, {target: rt}); | |
| t.strictEqual(rt.draggable, false); | |
| sensing.setDragMode({DRAG_MODE: 'draggable'}, {target: rt}); | |
| t.strictEqual(rt.draggable, true); | |
| t.end(); | |
| }); | |
| test('get loudness with caching', t => { | |
| const rt = new Runtime(); | |
| const sensing = new Sensing(rt); | |
| // It should report -1 when audio engine is not available. | |
| t.strictEqual(sensing.getLoudness(), -1); | |
| // Stub the audio engine with its getLoudness function, and set up different | |
| // values to simulate it changing over time. | |
| const firstLoudness = 1; | |
| const secondLoudness = 2; | |
| let simulatedLoudness = firstLoudness; | |
| rt.audioEngine = {getLoudness: () => simulatedLoudness}; | |
| // It should report -1 when current step time is null. | |
| // TW: The concept of a null current step time is inherently flawed and removed in TurboWarp. | |
| // t.strictEqual(sensing.getLoudness(), -1); | |
| // Stub the current step time. | |
| rt.currentStepTime = 1000 / 30; | |
| // The first time it works, it should report the result from the stubbed audio engine. | |
| t.strictEqual(sensing.getLoudness(), firstLoudness); | |
| // Update the simulated loudness to a new value. | |
| simulatedLoudness = secondLoudness; | |
| // Simulate time passing by advancing the timer forward a little bit. | |
| // After less than a step, it should still report cached loudness. | |
| let simulatedTime = Date.now() + (rt.currentStepTime / 2); | |
| sensing._timer = {time: () => simulatedTime}; | |
| t.strictEqual(sensing.getLoudness(), firstLoudness); | |
| // Simulate more than a step passing. It should now request the value | |
| // from the audio engine again. | |
| simulatedTime += rt.currentStepTime; | |
| t.strictEqual(sensing.getLoudness(), secondLoudness); | |
| t.end(); | |
| }); | |
| test('loud? boolean', t => { | |
| const rt = new Runtime(); | |
| const sensing = new Sensing(rt); | |
| // The simplest way to test this is to actually override the getLoudness | |
| // method, which isLoud uses. | |
| let simulatedLoudness = 0; | |
| sensing.getLoudness = () => simulatedLoudness; | |
| t.false(sensing.isLoud()); | |
| // Check for GREATER than 10, not equal. | |
| simulatedLoudness = 10; | |
| t.false(sensing.isLoud()); | |
| simulatedLoudness = 11; | |
| t.true(sensing.isLoud()); | |
| t.end(); | |
| }); | |
| test('get attribute of sprite variable', t => { | |
| const rt = new Runtime(); | |
| const sensing = new Sensing(rt); | |
| const s = new Sprite(null, rt); | |
| const target = new RenderedTarget(s, rt); | |
| const variable = { | |
| name: 'cars', | |
| value: 'trucks', | |
| type: '' | |
| }; | |
| // Add variable to set the map (it should be empty before this). | |
| target.variables.anId = variable; | |
| rt.getSpriteTargetByName = () => target; | |
| t.equal(sensing.getAttributeOf({PROPERTY: 'cars'}), 'trucks'); | |
| t.end(); | |
| }); | |
| test('get attribute of variable that does not exist', t => { | |
| const rt = new Runtime(); | |
| const sensing = new Sensing(rt); | |
| const s = new Sprite(null, rt); | |
| const target = new RenderedTarget(s, rt); | |
| rt.getTargetForStage = () => target; | |
| t.equal(sensing.getAttributeOf({PROPERTY: 'variableThatDoesNotExist'}), 0); | |
| t.end(); | |
| }); | |
| test('username block', t => { | |
| const rt = new Runtime(); | |
| const sensing = new Sensing(rt); | |
| const util = new BlockUtility(rt.sequencer); | |
| t.equal(sensing.getUsername({}, util), ''); | |
| t.end(); | |
| }); | |