diff --git a/src/blocks/scratch3_sensing.js b/src/blocks/scratch3_sensing.js index 18703d01d..2dcb23ed6 100644 --- a/src/blocks/scratch3_sensing.js +++ b/src/blocks/scratch3_sensing.js @@ -42,6 +42,7 @@ class Scratch3SensingBlocks { this.runtime.on('ANSWER', this._onAnswer.bind(this)); this.runtime.on('PROJECT_START', this._resetAnswer.bind(this)); this.runtime.on('PROJECT_STOP_ALL', this._clearAllQuestions.bind(this)); + this.runtime.on('STOP_FOR_TARGET', this._clearTargetQuestions.bind(this)); } /** @@ -134,6 +135,21 @@ class Scratch3SensingBlocks { this.runtime.emit('QUESTION', null); } + _clearTargetQuestions (stopTarget) { + const currentlyAsking = this._questionList.length > 0 && this._questionList[0][2] === stopTarget; + this._questionList = this._questionList.filter(question => ( + question[2] !== stopTarget + )); + + if (currentlyAsking) { + if (this._questionList.length > 0) { + this._askNextQuestion(); + } else { + this.runtime.emit('QUESTION', null); + } + } + } + askAndWait (args, util) { const _target = util.target; return new Promise(resolve => { diff --git a/src/engine/runtime.js b/src/engine/runtime.js index d634dbc83..6bc365267 100644 --- a/src/engine/runtime.js +++ b/src/engine/runtime.js @@ -377,6 +377,15 @@ class Runtime extends EventEmitter { return 'PROJECT_STOP_ALL'; } + /** + * Event name for target being stopped by a stop for target call. + * Used by blocks that need to stop individual targets. + * @const {string} + */ + static get STOP_FOR_TARGET () { + return 'STOP_FOR_TARGET'; + } + /** * Event name for visual value report. * @const {string} @@ -1382,6 +1391,9 @@ class Runtime extends EventEmitter { * @param {Thread=} optThreadException Optional thread to skip. */ stopForTarget (target, optThreadException) { + // Emit stop event to allow blocks to clean up any state. + this.emit(Runtime.STOP_FOR_TARGET, target, optThreadException); + // Stop any threads on the target. for (let i = 0; i < this.threads.length; i++) { if (this.threads[i] === optThreadException) { diff --git a/test/unit/blocks_sensing.js b/test/unit/blocks_sensing.js index 102cd61a6..1cecfff36 100644 --- a/test/unit/blocks_sensing.js +++ b/test/unit/blocks_sensing.js @@ -37,6 +37,90 @@ test('ask and answer with a hidden target', t => { }); }); +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}, 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}, thread: {}}; + const util2 = {target: {visible: false}, 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);