diff --git a/src/blocks/scratch3_sensing.js b/src/blocks/scratch3_sensing.js index 559b0e9c1..d9c2ab12f 100644 --- a/src/blocks/scratch3_sensing.js +++ b/src/blocks/scratch3_sensing.js @@ -7,6 +7,21 @@ class Scratch3SensingBlocks { * @type {Runtime} */ this.runtime = runtime; + + /** + * The "answer" block value. + * @type {string} + */ + this._answer = ''; + + /** + * The list of queued questions and respective `resolve` callbacks. + * @type {!Array} + */ + this._questionList = []; + + this.runtime.on('ANSWER', this._onAnswer.bind(this)); + this.runtime.on('PROJECT_STOP_ALL', this._clearAllQuestions.bind(this)); } /** @@ -28,10 +43,51 @@ class Scratch3SensingBlocks { sensing_keypressed: this.getKeyPressed, sensing_current: this.current, sensing_dayssince2000: this.daysSince2000, - sensing_loudness: this.getLoudness + sensing_loudness: this.getLoudness, + sensing_askandwait: this.askAndWait, + sensing_answer: this.getAnswer }; } + _onAnswer (answer) { + this._answer = answer; + const questionObj = this._questionList.shift(); + if (questionObj) { + const resolve = questionObj[1]; + resolve(); + this._askNextQuestion(); + } + } + + _enqueueAsk (question, resolve) { + this._questionList.push([question, resolve]); + } + + _askNextQuestion () { + if (this._questionList.length > 0) { + this.runtime.emit('QUESTION', this._questionList[0][0]); + } + } + + _clearAllQuestions () { + this._questionList = []; + this.runtime.emit('QUESTION', null); + } + + askAndWait (args) { + return new Promise(resolve => { + const isQuestionAsked = this._questionList.length > 0; + this._enqueueAsk(args.QUESTION, resolve); + if (!isQuestionAsked) { + this._askNextQuestion(); + } + }); + } + + getAnswer () { + return this._answer; + } + touchingObject (args, util) { const requestedObject = args.TOUCHINGOBJECTMENU; if (requestedObject === '_mouse_') { diff --git a/test/unit/blocks_sensing.js b/test/unit/blocks_sensing.js new file mode 100644 index 000000000..e0c60b4ba --- /dev/null +++ b/test/unit/blocks_sensing.js @@ -0,0 +1,34 @@ +const test = require('tap').test; +const Sensing = require('../../src/blocks/scratch3_sensing'); +const Runtime = require('../../src/engine/runtime'); + +test('getPrimitives', t => { + const rt = new Runtime(); + const s = new Sensing(rt); + t.type(s.getPrimitives(), 'object'); + t.end(); +}); + +test('ask and answer', t => { + const rt = new Runtime(); + const s = new Sensing(rt); + + 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}); + + // (3) Ask block resolves after the answer is emitted. + promise.then(() => { + t.strictEqual(s.getAnswer(), expectedAnswer); + t.end(); + }); +});