diff --git a/tests/mocha/.eslintrc.json b/tests/mocha/.eslintrc.json index 838abc187..7606fd3b2 100644 --- a/tests/mocha/.eslintrc.json +++ b/tests/mocha/.eslintrc.json @@ -17,14 +17,16 @@ "defineRowBlock": true, "defineStackBlock": true, "defineStatementBlock": true, - "createEventsFireStub": true, "createFireChangeListenerSpy": true, "createGenUidStubWithReturns": true, - "testAWorkspace": true, - "testHelpers" : true, + "getCategoryJSON": true, "getSimpleJSON": true, "getXmlArray": true, - "getCategoryJSON": true + "sharedTestSetup": true, + "sharedTestTeardown": true, + "testAWorkspace": true, + "testHelpers" : true, + "workspaceTeardown": true }, "rules": { "no-unused-vars": ["off"], diff --git a/tests/mocha/block_test.js b/tests/mocha/block_test.js index 3375c57bb..13982f14c 100644 --- a/tests/mocha/block_test.js +++ b/tests/mocha/block_test.js @@ -6,6 +6,7 @@ suite('Blocks', function() { setup(function() { + sharedTestSetup.call(this, {fireEventsNow: false}); this.workspace = new Blockly.Workspace(); Blockly.defineBlocksWithJsonArray([ { @@ -43,7 +44,7 @@ suite('Blocks', function() { }]); }); teardown(function() { - this.workspace.dispose(); + sharedTestTeardown.call(this); delete Blockly.Blocks['empty_block']; delete Blockly.Blocks['stack_block']; delete Blockly.Blocks['row_block']; @@ -392,8 +393,6 @@ suite('Blocks', function() { }); suite('Connection Tracking', function() { setup(function() { - this.workspace.dispose(); - // The new rendered workspace will get disposed by the parent teardown. this.workspace = Blockly.inject('blocklyDiv'); this.getInputs = function() { @@ -419,12 +418,11 @@ suite('Blocks', function() { chai.assert.isEmpty(this.getNext()); chai.assert.isEmpty(this.getPrevious()); }; - - this.clock = sinon.useFakeTimers(); }); teardown(function() { - this.clock.restore(); + workspaceTeardown.call(this, this.workspace); }); + suite('Deserialization', function() { test('Stack', function() { Blockly.Xml.appendDomToWorkspace(Blockly.Xml.textToDom( @@ -433,7 +431,7 @@ suite('Blocks', function() { '' ), this.workspace); this.assertConnectionsEmpty(); - this.clock.tick(1); + this.clock.runAll(); chai.assert.equal(this.getPrevious().length, 1); chai.assert.equal(this.getNext().length, 1); }); @@ -452,7 +450,7 @@ suite('Blocks', function() { '' ), this.workspace); this.assertConnectionsEmpty(); - this.clock.tick(1); + this.clock.runAll(); chai.assert.equal(this.getPrevious().length, 3); chai.assert.equal(this.getNext().length, 3); }); @@ -463,7 +461,7 @@ suite('Blocks', function() { '' ), this.workspace); this.assertConnectionsEmpty(); - this.clock.tick(1); + this.clock.runAll(); chai.assert.equal(this.getPrevious().length, 1); chai.assert.equal(this.getNext().length, 1); }); @@ -482,7 +480,7 @@ suite('Blocks', function() { '' ), this.workspace); this.assertConnectionsEmpty(); - this.clock.tick(1); + this.clock.runAll(); chai.assert.equal(this.getPrevious().length, 3); chai.assert.equal(this.getNext().length, 3); }); @@ -493,7 +491,7 @@ suite('Blocks', function() { '' ), this.workspace); this.assertConnectionsEmpty(); - this.clock.tick(1); + this.clock.runAll(); chai.assert.equal(this.getOutputs().length, 1); chai.assert.equal(this.getInputs().length, 1); }); @@ -512,7 +510,7 @@ suite('Blocks', function() { '' ), this.workspace); this.assertConnectionsEmpty(); - this.clock.tick(1); + this.clock.runAll(); chai.assert.equal(this.getOutputs().length, 3); chai.assert.equal(this.getInputs().length, 3); }); @@ -523,7 +521,7 @@ suite('Blocks', function() { '' ), this.workspace); this.assertConnectionsEmpty(); - this.clock.tick(1); + this.clock.runAll(); chai.assert.equal(this.getOutputs().length, 1); chai.assert.equal(this.getInputs().length, 0); }); @@ -542,7 +540,7 @@ suite('Blocks', function() { '' ), this.workspace); this.assertConnectionsEmpty(); - this.clock.tick(1); + this.clock.runAll(); chai.assert.equal(this.getOutputs().length, 1); chai.assert.equal(this.getInputs().length, 0); }); @@ -561,7 +559,7 @@ suite('Blocks', function() { '' ), this.workspace); this.assertConnectionsEmpty(); - this.clock.tick(1); + this.clock.runAll(); chai.assert.equal(this.getOutputs().length, 2); chai.assert.equal(this.getInputs().length, 1); }); @@ -572,7 +570,7 @@ suite('Blocks', function() { '' ), this.workspace); this.assertConnectionsEmpty(); - this.clock.tick(1); + this.clock.runAll(); chai.assert.equal(this.getPrevious().length, 1); chai.assert.equal(this.getNext().length, 2); }); @@ -591,7 +589,7 @@ suite('Blocks', function() { '' ), this.workspace); this.assertConnectionsEmpty(); - this.clock.tick(1); + this.clock.runAll(); chai.assert.equal(this.getPrevious().length, 3); chai.assert.equal(this.getNext().length, 6); }); @@ -602,7 +600,7 @@ suite('Blocks', function() { '' ), this.workspace); this.assertConnectionsEmpty(); - this.clock.tick(1); + this.clock.runAll(); chai.assert.equal(this.getPrevious().length, 1); chai.assert.equal(this.getNext().length, 1); }); @@ -621,7 +619,7 @@ suite('Blocks', function() { '' ), this.workspace); this.assertConnectionsEmpty(); - this.clock.tick(1); + this.clock.runAll(); chai.assert.equal(this.getPrevious().length, 1); chai.assert.equal(this.getNext().length, 1); }); @@ -640,7 +638,7 @@ suite('Blocks', function() { '' ), this.workspace); this.assertConnectionsEmpty(); - this.clock.tick(1); + this.clock.runAll(); chai.assert.equal(this.getPrevious().length, 2); chai.assert.equal(this.getNext().length, 3); }); @@ -679,7 +677,7 @@ suite('Blocks', function() { var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom( '' ), this.workspace); - this.clock.tick(1); + this.clock.runAll(); chai.assert.equal(this.getPrevious().length, 1); chai.assert.equal(this.getNext().length, 1); @@ -704,7 +702,7 @@ suite('Blocks', function() { '' ), this.workspace); this.assertConnectionsEmpty(); - this.clock.tick(1); + this.clock.runAll(); chai.assert.equal(this.getPrevious().length, 3); chai.assert.equal(this.getNext().length, 3); @@ -720,7 +718,7 @@ suite('Blocks', function() { var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom( '' ), this.workspace); - this.clock.tick(1); + this.clock.runAll(); chai.assert.equal(this.getOutputs().length, 1); chai.assert.equal(this.getInputs().length, 1); @@ -744,7 +742,7 @@ suite('Blocks', function() { ' ' + '' ), this.workspace); - this.clock.tick(1); + this.clock.runAll(); chai.assert.equal(this.getOutputs().length, 3); chai.assert.equal(this.getInputs().length, 3); @@ -768,7 +766,7 @@ suite('Blocks', function() { ' ' + '' ), this.workspace); - this.clock.tick(1); + this.clock.runAll(); chai.assert.equal(this.getOutputs().length, 3); chai.assert.equal(this.getInputs().length, 3); @@ -795,7 +793,7 @@ suite('Blocks', function() { ' ' + '' ), this.workspace); - this.clock.tick(1); + this.clock.runAll(); chai.assert.equal(this.getOutputs().length, 3); chai.assert.equal(this.getInputs().length, 3); @@ -820,7 +818,7 @@ suite('Blocks', function() { var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom( '' ), this.workspace); - this.clock.tick(1); + this.clock.runAll(); chai.assert.equal(this.getPrevious().length, 1); chai.assert.equal(this.getNext().length, 2); @@ -845,7 +843,7 @@ suite('Blocks', function() { '' ), this.workspace); this.assertConnectionsEmpty(); - this.clock.tick(1); + this.clock.runAll(); chai.assert.equal(this.getPrevious().length, 3); chai.assert.equal(this.getNext().length, 6); @@ -870,7 +868,7 @@ suite('Blocks', function() { '' ), this.workspace); this.assertConnectionsEmpty(); - this.clock.tick(1); + this.clock.runAll(); chai.assert.equal(this.getPrevious().length, 3); chai.assert.equal(this.getNext().length, 6); @@ -896,7 +894,7 @@ suite('Blocks', function() { '' ), this.workspace); this.assertConnectionsEmpty(); - this.clock.tick(1); + this.clock.runAll(); chai.assert.equal(this.getPrevious().length, 3); chai.assert.equal(this.getNext().length, 6); @@ -1019,12 +1017,6 @@ suite('Blocks', function() { }); }); suite('Comments', function() { - setup(function() { - this.eventSpy = sinon.spy(Blockly.Events, 'fire'); - }); - teardown(function() { - this.eventSpy.restore(); - }); suite('Set/Get Text', function() { function assertCommentEvent(eventSpy, oldValue, newValue) { var calls = eventSpy.getCalls(); @@ -1039,6 +1031,12 @@ suite('Blocks', function() { var event = calls[calls.length - 1].args[0]; chai.assert.notEqual(event.type, Blockly.Events.BLOCK_CHANGE); } + setup(function() { + this.eventsFireSpy = sinon.spy(Blockly.Events, 'fire'); + }); + teardown(function() { + this.eventsFireSpy.restore(); + }); suite('Headless', function() { setup(function() { this.block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom( @@ -1048,24 +1046,24 @@ suite('Blocks', function() { test('Text', function() { this.block.setCommentText('test text'); chai.assert.equal(this.block.getCommentText(), 'test text'); - assertCommentEvent(this.eventSpy, null, 'test text'); + assertCommentEvent(this.eventsFireSpy, null, 'test text'); }); test('Text Empty', function() { this.block.setCommentText(''); chai.assert.equal(this.block.getCommentText(), ''); - assertCommentEvent(this.eventSpy, null, ''); + assertCommentEvent(this.eventsFireSpy, null, ''); }); test('Text Null', function() { this.block.setCommentText(null); chai.assert.isNull(this.block.getCommentText()); - assertNoCommentEvent(this.eventSpy); + assertNoCommentEvent(this.eventsFireSpy); }); test('Text -> Null', function() { this.block.setCommentText('first text'); this.block.setCommentText(null); chai.assert.isNull(this.block.getCommentText()); - assertCommentEvent(this.eventSpy, 'first text', null); + assertCommentEvent(this.eventsFireSpy, 'first text', null); }); }); suite('Rendered', function() { @@ -1084,24 +1082,24 @@ suite('Blocks', function() { test('Text', function() { this.block.setCommentText('test text'); chai.assert.equal(this.block.getCommentText(), 'test text'); - assertCommentEvent(this.eventSpy, null, 'test text'); + assertCommentEvent(this.eventsFireSpy, null, 'test text'); }); test('Text Empty', function() { this.block.setCommentText(''); chai.assert.equal(this.block.getCommentText(), ''); - assertCommentEvent(this.eventSpy, null, ''); + assertCommentEvent(this.eventsFireSpy, null, ''); }); test('Text Null', function() { this.block.setCommentText(null); chai.assert.isNull(this.block.getCommentText()); - assertNoCommentEvent(this.eventSpy); + assertNoCommentEvent(this.eventsFireSpy); }); test('Text -> Null', function() { this.block.setCommentText('first text'); this.block.setCommentText(null); chai.assert.isNull(this.block.getCommentText()); - assertCommentEvent(this.eventSpy, 'first text', null); + assertCommentEvent(this.eventsFireSpy, 'first text', null); }); test('Set While Visible - Editable', function() { this.block.setCommentText('test1'); @@ -1110,22 +1108,21 @@ suite('Blocks', function() { this.block.setCommentText('test2'); chai.assert.equal(this.block.getCommentText(), 'test2'); - assertCommentEvent(this.eventSpy, 'test1', 'test2'); + assertCommentEvent(this.eventsFireSpy, 'test1', 'test2'); chai.assert.equal(icon.textarea_.value, 'test2'); }); test('Set While Visible - NonEditable', function() { this.block.setCommentText('test1'); - var editableStub = sinon.stub(this.block, 'isEditable').returns(false); + // Restored up by call to sinon.restore() in sharedTestTeardown() + sinon.stub(this.block, 'isEditable').returns(false); var icon = this.block.getCommentIcon(); icon.setVisible(true); this.block.setCommentText('test2'); chai.assert.equal(this.block.getCommentText(), 'test2'); - assertCommentEvent(this.eventSpy, 'test1', 'test2'); + assertCommentEvent(this.eventsFireSpy, 'test1', 'test2'); chai.assert.equal(icon.paragraphElement_.firstChild.textContent, 'test2'); - - editableStub.restore(); }); test('Get Text While Editing', function() { this.block.setCommentText('test1'); diff --git a/tests/mocha/comment_test.js b/tests/mocha/comment_test.js index ace22c603..53215f048 100644 --- a/tests/mocha/comment_test.js +++ b/tests/mocha/comment_test.js @@ -6,6 +6,7 @@ suite('Comments', function() { setup(function() { + sharedTestSetup.call(this); Blockly.defineBlocksWithJsonArray([ { "type": "empty_block", @@ -25,16 +26,12 @@ suite('Comments', function() { this.comment.computeIconLocation(); }); teardown(function() { + sharedTestTeardown.call(this); delete Blockly.Blocks['empty_block']; - this.workspace.dispose(); }); suite('Visibility and Editability', function() { setup(function() { this.comment.setText('test text'); - this.eventsStub = createEventsFireStub(); - }); - teardown(function() { - sinon.restore(); }); function assertEditable(comment) { @@ -53,7 +50,7 @@ suite('Comments', function() { chai.assert.isTrue(this.comment.isVisible()); assertEditable(this.comment); assertLastCallEventArgEquals( - this.eventsStub, Blockly.Events.UI, this.workspace.id, this.block.id, + this.eventsFireStub, Blockly.Events.UI, this.workspace.id, this.block.id, {element: 'commentOpen', oldValue: false, newValue: true}); }); test('Not Editable', function() { @@ -63,7 +60,7 @@ suite('Comments', function() { chai.assert.isTrue(this.comment.isVisible()); assertNotEditable(this.comment); assertLastCallEventArgEquals( - this.eventsStub, Blockly.Events.UI, this.workspace.id, this.block.id, + this.eventsFireStub, Blockly.Events.UI, this.workspace.id, this.block.id, {element: 'commentOpen', oldValue: false, newValue: true}); }); test('Editable -> Not Editable', function() { @@ -73,10 +70,10 @@ suite('Comments', function() { this.comment.updateEditable(); chai.assert.isTrue(this.comment.isVisible()); assertNotEditable(this.comment);assertLastCallEventArgEquals( - this.eventsStub, Blockly.Events.UI, this.workspace.id, this.block.id, + this.eventsFireStub, Blockly.Events.UI, this.workspace.id, this.block.id, {element: 'commentOpen', oldValue: false, newValue: true}); assertLastCallEventArgEquals( - this.eventsStub, Blockly.Events.UI, this.workspace.id, this.block.id, + this.eventsFireStub, Blockly.Events.UI, this.workspace.id, this.block.id, {element: 'commentOpen', oldValue: false, newValue: true}); }); test('Not Editable -> Editable', function() { @@ -88,7 +85,7 @@ suite('Comments', function() { chai.assert.isTrue(this.comment.isVisible()); assertEditable(this.comment); assertLastCallEventArgEquals( - this.eventsStub, Blockly.Events.UI, this.workspace.id, this.block.id, + this.eventsFireStub, Blockly.Events.UI, this.workspace.id, this.block.id, {element: 'commentOpen', oldValue: false, newValue: true}); }); }); diff --git a/tests/mocha/event_test.js b/tests/mocha/event_test.js index d5fa8576d..00b58fb68 100644 --- a/tests/mocha/event_test.js +++ b/tests/mocha/event_test.js @@ -6,6 +6,8 @@ suite('Events', function() { setup(function() { + sharedTestSetup.call(this, {fireEventsNow: false}); + this.eventsFireSpy = sinon.spy(Blockly.Events, 'fire'); this.workspace = new Blockly.Workspace(); Blockly.defineBlocksWithJsonArray([{ 'type': 'field_variable_test_block', @@ -25,13 +27,9 @@ suite('Events', function() { }); teardown(function() { + sharedTestTeardown.call(this); delete Blockly.Blocks['field_variable_test_block']; delete Blockly.Blocks['simple_test_block']; - this.workspace.dispose(); - - // Clear Blockly.Event state. - Blockly.Events.setGroup(false); - Blockly.Events.disabled_ = 0; }); function createSimpleTestBlock(workspace, opt_prototypeName) { @@ -75,9 +73,6 @@ suite('Events', function() { [this.TEST_BLOCK_ID, this.TEST_PARENT_ID]); this.block = createSimpleTestBlock(this.workspace); }); - teardown(function() { - sinon.restore(); - }); test('Block base', function() { var event = new Blockly.Events.BlockBase(this.block); @@ -232,9 +227,6 @@ suite('Events', function() { this.block = createSimpleTestBlock(this.workspace, 'field_variable_test_block'); }); - teardown(function() { - sinon.restore(); - }); test('Change', function() { var event = new Blockly.Events.Change( @@ -625,18 +617,17 @@ suite('Events', function() { suite('Firing', function() { setup(function() { - this.eventsStub = createEventsFireStub(); this.changeListenerSpy = createFireChangeListenerSpy(this.workspace); }); - teardown(function() { - sinon.restore(); - }); - test('Block dispose triggers Delete', function() { try { var toolbox = document.getElementById('toolbox-categories'); var workspaceSvg = Blockly.inject('blocklyDiv', {toolbox: toolbox}); + var changeListenerSpy = createFireChangeListenerSpy(workspaceSvg); + var TEST_BLOCK_ID = 'test_block_id'; + var genUidStub = createGenUidStubWithReturns( + [TEST_BLOCK_ID, 'test_group_id']); var block = workspaceSvg.newBlock(''); block.initSvg(); @@ -646,9 +637,22 @@ suite('Events', function() { block.dispose(); + // Run all queued events. + this.clock.runAll(); + + // Expect two calls to genUid: one to set the block's ID, and one for + // the event group's ID for creating block. + sinon.assert.calledTwice(genUidStub); + assertLastCallEventArgEquals( - this.eventsStub, Blockly.Events.DELETE, workspaceSvg.id, - expectedId, {oldXml: expectedOldXml}); + this.eventsFireSpy, Blockly.Events.DELETE, workspaceSvg.id, + expectedId, {oldXml: expectedOldXml, group: ''}); + assertLastCallEventArgEquals( + changeListenerSpy, Blockly.Events.DELETE, workspaceSvg.id, + expectedId, {oldXml: expectedOldXml, group: ''}); + + // Expect the workspace to not have a variable with ID 'test_block_id'. + chai.assert.isNull(this.workspace.getVariableById(TEST_BLOCK_ID)); } finally { workspaceSvg.dispose(); } @@ -663,12 +667,15 @@ suite('Events', function() { var _ = this.workspace.newBlock('field_variable_test_block'); var TEST_VAR_NAME = 'item'; // As defined in block's json. + // Run all queued events. + this.clock.runAll(); + // Expect three calls to genUid: one to set the block's ID, one for the event - // group's id, and one for the variable's ID. + // group's ID, and one for the variable's ID. sinon.assert.calledThrice(genUidStub); // Expect two events fired: varCreate and block create. - sinon.assert.calledTwice(this.eventsStub); + sinon.assert.calledTwice(this.eventsFireSpy); // Expect both events to trigger change listener. sinon.assert.calledTwice(this.changeListenerSpy); // Both events should be on undo stack @@ -701,6 +708,9 @@ suite('Events', function() { var TEST_VAR_ID = 'test_var_id'; var TEST_VAR_NAME = 'name1'; + // Run all queued events. + this.clock.runAll(); + // Expect one call to genUid: for the event group's id sinon.assert.calledOnce(genUidStub); @@ -710,7 +720,7 @@ suite('Events', function() { // 3. block create // 4. move (no-op, is filtered out) // 5. finished loading - sinon.assert.callCount(this.eventsStub, 5); + sinon.assert.callCount(this.eventsFireSpy, 5); // The first varCreate and move event should have been ignored. sinon.assert.callCount(this.changeListenerSpy, 3); // Expect two events on undo stack: varCreate and block create. diff --git a/tests/mocha/procedures_test.js b/tests/mocha/procedures_test.js index 32752ee57..4616e252b 100644 --- a/tests/mocha/procedures_test.js +++ b/tests/mocha/procedures_test.js @@ -9,6 +9,7 @@ goog.require('Blockly.Msg'); suite('Procedures', function() { setup(function() { + sharedTestSetup.call(this); this.workspace = new Blockly.Workspace(); this.callForAllTypes = function(func, startName) { @@ -37,7 +38,7 @@ suite('Procedures', function() { }; }); teardown(function() { - this.workspace.dispose(); + sharedTestTeardown.call(this); }); suite('allProcedures', function() { @@ -340,7 +341,6 @@ suite('Procedures', function() { }); suite('Enable/Disable', function() { setup(function() { - createEventsFireStub(); var toolbox = document.getElementById('toolbox-categories'); this.workspaceSvg = Blockly.inject('blocklyDiv', {toolbox: toolbox}); }); diff --git a/tests/mocha/test_helpers.js b/tests/mocha/test_helpers.js index f65c70606..b0c0f8e63 100644 --- a/tests/mocha/test_helpers.js +++ b/tests/mocha/test_helpers.js @@ -39,10 +39,114 @@ function captureWarnings(innerFunc) { return msgs; } +/** + * Safely disposes of Blockly workspace, logging any errors. + * Assumes that sharedTestSetup has also been called. This should be called + * using workspaceTeardown.call(this). + * @param {!Blockly.Workspace} workspace The workspace to dispose. + */ +function workspaceTeardown(workspace) { + try { + this.clock.runAll(); // Run all queued setTimeout calls. + workspace.dispose(); + this.clock.runAll(); // Run all remaining queued setTimeout calls. + } catch (e) { + console.error(this.currentTest.fullTitle() + '\n', e); + } +} + +/** + * Creates stub for Blockly.Events.fire that advances the clock forward after + * the event fires so it is processed immediately instead of on a timeout. + * @param {!SinonClock} clock The sinon clock. + * @return {!SinonStub} The created stub. + * @private + */ +function createEventsFireStubFireImmediately_(clock) { + var stub = sinon.stub(Blockly.Events, 'fire'); + stub.callsFake(function(event) { + // TODO(#4070) Replace the fake function content with the following code + // that uses wrappedMethod after cleanup is added to ALL tests. + // Calling the original method will not consistently work if other tests + // add things to the event queue because the setTimeout call will not be + // added to the stubbed clock (so runAll cannot be used to control). + // // Call original method. + // stub.wrappedMethod.call(this, ...arguments); + // // Advance clock forward to run any queued events. + // clock.runAll(); + // + if (!Blockly.Events.isEnabled()) { + return; + } + Blockly.Events.FIRE_QUEUE_.push(event); + Blockly.Events.fireNow_(); + }); + return stub; +} + +/** + * Shared setup method that sets up fake timer for clock so that pending + * setTimeout calls can be cleared in test teardown along with other common + * stubs. Should be called in setup of outermost suite using + * sharedTestSetup.call(this). + * @param {Object} options Options to enable/disable setup + * of certain stubs. + */ +function sharedTestSetup(options = {}) { + this.sharedSetupCalled_ = true; + // Sandbox created for greater control when certain stubs are cleared. + this.sharedSetupSandbox_ = sinon.createSandbox(); + this.clock = this.sharedSetupSandbox_.useFakeTimers(); + if (options['fireEventsNow'] === undefined || options['fireEventsNow']) { + // Stubs event firing unless passed option "fireEventsNow: false" + this.eventsFireStub = createEventsFireStubFireImmediately_(this.clock); + } +} + +/** + * Shared cleanup method that clears up pending setTimeout calls, disposes of + * workspace, and resets global variables. Should be called in setup of + * outermost suite using sharedTestTeardown.call(this). + */ +function sharedTestTeardown() { + if (!this.sharedSetupCalled_) { + console.error('"' + this.currentTest.fullTitle() + + '" did not call sharedTestSetup'); + } + + try { + if (this.workspace) { + workspaceTeardown.call(this, this.workspace); + this.workspace = null; + } else { + this.clock.runAll(); // Run all queued setTimeout calls. + } + } catch (e) { + console.error(this.currentTest.fullTitle() + '\n', e); + } finally { + // Clear Blockly.Event state. + Blockly.Events.setGroup(false); + Blockly.Events.disabled_ = 0; + if (Blockly.Events.FIRE_QUEUE_.length) { + // If this happens, it may mean that some previous test is missing cleanup + // (i.e. a previous test added an event to the queue on a timeout that + // did not use a stubbed clock). + Blockly.Events.FIRE_QUEUE_.length = 0; + console.warn(this.currentTest.fullTitle() + + '" needed cleanup of Blockly.Events.FIRE_QUEUE_. This may indicate ' + + 'leakage from an earlier test'); + } + + // Restore all stubbed methods. + this.sharedSetupSandbox_.restore(); + sinon.restore(); + } +} + /** * Creates stub for Blockly.utils.genUid that returns the provided id or ids. - * Recommended to also assert that the stub is called the expected number of - * times. + * Recommended to also assert that the stub is called the expected number of + * times. * @param {string|!Array} returnIds The return values to use for the * created stub. If a single value is passed, then the stub always returns * that value. @@ -60,27 +164,6 @@ function createGenUidStubWithReturns(returnIds) { return stub; } -/** - * Creates stub for Blockly.Events.fire that fires events immediately instead of - * with timeout. - * @return {!SinonStub} The created stub. - */ -function createEventsFireStub() { - // TODO(#4064): Remove clearing of event clear here in favor of adding cleanup - // to other tests that cause events to be added to the queue even after they - // end. - Blockly.Events.FIRE_QUEUE_.length = 0; - var stub = sinon.stub(Blockly.Events, 'fire'); - stub.callsFake(function(event) { - if (!Blockly.Events.isEnabled()) { - return; - } - Blockly.Events.FIRE_QUEUE_.push(event); - Blockly.Events.fireNow_(); - }); - return stub; -} - /** * Creates spy for workspace fireChangeListener * @param {!Blockly.Workspace} workspace The workspace to spy fireChangeListener @@ -124,7 +207,7 @@ function assertEventEquals(event, expectedType, /** * Asserts that the event passed to the last call of the given spy has the - * expected values. Assumes that the event is passed as the first argument. + * expected values. Assumes that the event is passed as the first argument. * @param {!SinonSpy} spy The spy to use. * @param {string} expectedType Expected type of event fired. * @param {string} expectedWorkspaceId Expected workspace id of event fired. @@ -142,7 +225,7 @@ function assertLastCallEventArgEquals(spy, expectedType, /** * Asserts that the event passed to the nth call of the given spy has the - * expected values. Assumes that the event is passed as the first argument. + * expected values. Assumes that the event is passed as the first argument. * @param {!SinonSpy} spy The spy to use. * @param {number} n Which call to check. * @param {string} expectedType Expected type of event fired. diff --git a/tests/mocha/trashcan_test.js b/tests/mocha/trashcan_test.js index 2256afc7a..1ffcf5bb4 100644 --- a/tests/mocha/trashcan_test.js +++ b/tests/mocha/trashcan_test.js @@ -26,7 +26,7 @@ suite("Trashcan", function() { } setup(function() { - this.eventsStub = createEventsFireStub(); + sharedTestSetup.call(this); var options = new Blockly.Options( {'trashcan': true, 'maxTrashcanContents': Infinity}); this.workspace = new Blockly.WorkspaceSvg(options); @@ -35,7 +35,7 @@ suite("Trashcan", function() { this.trashcan.svgLid_ = sinon.createStubInstance(SVGElement); }); teardown(function() { - sinon.restore(); + sharedTestTeardown.call(this); }); suite("Events", function() { @@ -63,7 +63,7 @@ suite("Trashcan", function() { }); test("Click without contents - fires no events", function() { this.trashcan.click(); - var lastFireCall = this.eventsStub.lastCall; + var lastFireCall = this.eventsFireStub.lastCall; chai.assert.notExists(lastFireCall); }); test("Click with contents - fires trashcanOpen", function() { @@ -73,7 +73,7 @@ suite("Trashcan", function() { var showFlyoutStub = sinon.stub(this.trashcan.flyout, "show"); this.trashcan.click(); assertLastCallEventArgEquals( - this.eventsStub, Blockly.Events.UI, this.workspace.id, undefined, + this.eventsFireStub, Blockly.Events.UI, this.workspace.id, undefined, {element: 'trashcanOpen', oldValue: null, newValue: true}); sinon.assert.calledOnce(showFlyoutStub); }); diff --git a/tests/mocha/workspace_comment_test.js b/tests/mocha/workspace_comment_test.js index 4c42a816e..421633e18 100644 --- a/tests/mocha/workspace_comment_test.js +++ b/tests/mocha/workspace_comment_test.js @@ -8,11 +8,12 @@ goog.require('Blockly.WorkspaceComment'); suite('Workspace comment', function() { setup(function() { + sharedTestSetup.call(this); this.workspace = new Blockly.Workspace(); }); teardown(function() { - this.workspace.dispose(); + sharedTestTeardown.call(this); }); suite('getTopComments(ordered=true)', function() { @@ -163,7 +164,6 @@ suite('Workspace comment', function() { suite('Content', function() { setup(function() { - createEventsFireStub(); this.comment = new Blockly.WorkspaceComment( this.workspace, 'comment text', 0, 0, 'comment id'); diff --git a/tests/mocha/workspace_svg_test.js b/tests/mocha/workspace_svg_test.js index 5bfe4375f..5e7487552 100644 --- a/tests/mocha/workspace_svg_test.js +++ b/tests/mocha/workspace_svg_test.js @@ -6,6 +6,7 @@ suite('WorkspaceSvg', function() { setup(function() { + sharedTestSetup.call(this); var toolbox = document.getElementById('toolbox-categories'); this.workspace = Blockly.inject('blocklyDiv', {toolbox: toolbox}); Blockly.defineBlocksWithJsonArray([{ @@ -26,10 +27,9 @@ suite('WorkspaceSvg', function() { }); teardown(function() { + sharedTestTeardown.call(this); delete Blockly.Blocks['simple_test_block']; delete Blockly.Blocks['test_val_in']; - this.workspace.dispose(); - sinon.restore(); }); test('appendDomToWorkspace alignment', function() { diff --git a/tests/mocha/workspace_test.js b/tests/mocha/workspace_test.js index 8a3119640..7714e1aae 100644 --- a/tests/mocha/workspace_test.js +++ b/tests/mocha/workspace_test.js @@ -6,11 +6,12 @@ suite('Workspace', function() { setup(function() { + sharedTestSetup.call(this); this.workspace = new Blockly.Workspace(); }); teardown(function() { - this.workspace.dispose(); + sharedTestTeardown.call(this); }); // eslint-disable-next-line no-use-before-define @@ -663,10 +664,6 @@ function testAWorkspace() { }); suite('Undo/Redo', function() { - setup(function() { - createEventsFireStub(); - }); - function createTwoVarsDifferentTypes(workspace) { workspace.createVariable('name1', 'type1', 'id1'); workspace.createVariable('name2', 'type2', 'id2');