From 3868db322141e2e9f4d20329e56697420cd247f3 Mon Sep 17 00:00:00 2001 From: Monica Kozbial Date: Fri, 24 Jul 2020 14:49:39 -0700 Subject: [PATCH] Refactor shared (#4066) * Refactoring event helpers. * Fix concurrent test failure. --- tests/mocha/.eslintrc.json | 5 + tests/mocha/comment_test.js | 50 ++-- tests/mocha/event_test.js | 365 ++++++++++++++++++----------- tests/mocha/field_variable_test.js | 6 +- tests/mocha/gesture_test.js | 1 - tests/mocha/test_helpers.js | 105 ++++++++- tests/mocha/trashcan_test.js | 168 ++++++------- tests/mocha/variable_map_test.js | 4 +- tests/mocha/xml_test.js | 6 +- 9 files changed, 452 insertions(+), 258 deletions(-) diff --git a/tests/mocha/.eslintrc.json b/tests/mocha/.eslintrc.json index 70e611cad..838abc187 100644 --- a/tests/mocha/.eslintrc.json +++ b/tests/mocha/.eslintrc.json @@ -8,6 +8,9 @@ "chai": false, "sinon": false, "assertArrayEquals": true, + "assertEventEquals": true, + "assertLastCallEventArgEquals": true, + "assertNthCallEventArgEquals": true, "assertVariableValues": true, "captureWarnings": true, "createTestBlock": true, @@ -15,6 +18,8 @@ "defineStackBlock": true, "defineStatementBlock": true, "createEventsFireStub": true, + "createFireChangeListenerSpy": true, + "createGenUidStubWithReturns": true, "testAWorkspace": true, "testHelpers" : true, "getSimpleJSON": true, diff --git a/tests/mocha/comment_test.js b/tests/mocha/comment_test.js index a24f68c95..ace22c603 100644 --- a/tests/mocha/comment_test.js +++ b/tests/mocha/comment_test.js @@ -31,19 +31,12 @@ suite('Comments', function() { suite('Visibility and Editability', function() { setup(function() { this.comment.setText('test text'); - this.eventSpy = sinon.stub(Blockly.Events, 'fire'); + this.eventsStub = createEventsFireStub(); }); teardown(function() { - this.eventSpy.restore(); + sinon.restore(); }); - function assertEvent(eventSpy, type, element, oldValue, newValue) { - var calls = eventSpy.getCalls(); - var event = calls[calls.length - 1].args[0]; - chai.assert.equal(event.type, type); - chai.assert.equal(event.element, element); - chai.assert.equal(event.oldValue, oldValue); - chai.assert.equal(event.newValue, newValue); - } + function assertEditable(comment) { chai.assert.isNotOk(comment.paragraphElement_); chai.assert.isOk(comment.textarea_); @@ -59,28 +52,32 @@ suite('Comments', function() { this.comment.setVisible(true); chai.assert.isTrue(this.comment.isVisible()); assertEditable(this.comment); - assertEvent(this.eventSpy, Blockly.Events.UI, 'commentOpen', false, true); + assertLastCallEventArgEquals( + this.eventsStub, Blockly.Events.UI, this.workspace.id, this.block.id, + {element: 'commentOpen', oldValue: false, newValue: true}); }); test('Not Editable', function() { - var editableStub = sinon.stub(this.block, 'isEditable').returns(false); + sinon.stub(this.block, 'isEditable').returns(false); this.comment.setVisible(true); chai.assert.isTrue(this.comment.isVisible()); assertNotEditable(this.comment); - assertEvent(this.eventSpy, Blockly.Events.UI, 'commentOpen', false, true); - - editableStub.restore(); + assertLastCallEventArgEquals( + this.eventsStub, Blockly.Events.UI, this.workspace.id, this.block.id, + {element: 'commentOpen', oldValue: false, newValue: true}); }); test('Editable -> Not Editable', function() { this.comment.setVisible(true); - var editableStub = sinon.stub(this.block, 'isEditable').returns(false); + sinon.stub(this.block, 'isEditable').returns(false); this.comment.updateEditable(); chai.assert.isTrue(this.comment.isVisible()); - assertNotEditable(this.comment); - assertEvent(this.eventSpy, Blockly.Events.UI, 'commentOpen', false, true); - - editableStub.restore(); + assertNotEditable(this.comment);assertLastCallEventArgEquals( + this.eventsStub, 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, + {element: 'commentOpen', oldValue: false, newValue: true}); }); test('Not Editable -> Editable', function() { var editableStub = sinon.stub(this.block, 'isEditable').returns(false); @@ -90,12 +87,15 @@ suite('Comments', function() { this.comment.updateEditable(); chai.assert.isTrue(this.comment.isVisible()); assertEditable(this.comment); - assertEvent(this.eventSpy, Blockly.Events.UI, 'commentOpen', false, true); - - editableStub.restore(); + assertLastCallEventArgEquals( + this.eventsStub, Blockly.Events.UI, this.workspace.id, this.block.id, + {element: 'commentOpen', oldValue: false, newValue: true}); }); }); suite('Set/Get Bubble Size', function() { + teardown(function() { + sinon.restore(); + }); function assertBubbleSize(comment, height, width) { var size = comment.getBubbleSize(); chai.assert.equal(size.height, height); @@ -111,12 +111,10 @@ suite('Comments', function() { assertBubbleSizeDefault(this.comment); this.comment.setBubbleSize(100, 100); assertBubbleSize(this.comment, 100, 100); - chai.assert(bubbleSizeSpy.calledOnce); + sinon.assert.calledOnce(bubbleSizeSpy); this.comment.setVisible(false); assertBubbleSize(this.comment, 100, 100); - - bubbleSizeSpy.restore(); }); test('Set Size While Invisible', function() { assertBubbleSizeDefault(this.comment); diff --git a/tests/mocha/event_test.js b/tests/mocha/event_test.js index 6cf675875..c7d0bbcd5 100644 --- a/tests/mocha/event_test.js +++ b/tests/mocha/event_test.js @@ -34,30 +34,6 @@ suite('Events', function() { Blockly.Events.disabled_ = 0; }); - function checkExactEventValues(event, values) { - var keys = Object.keys(values); - for (var i = 0; i < keys.length; i++) { - var field = keys[i]; - chai.assert.equal(values[field], event[field]); - } - } - - function checkCreateEventValues(event, block, ids, type) { - var expected_xml = Blockly.Xml.domToText(Blockly.Xml.blockToDom(block)); - var result_xml = Blockly.Xml.domToText(event.xml); - chai.assert.equal(expected_xml, result_xml); - chai.assert.deepEqual(ids, event.ids); - chai.assert.equal(type, event.type); - } - - function checkDeleteEventValues(event, block, ids, type) { - var expected_xml = Blockly.Xml.domToText(Blockly.Xml.blockToDom(block)); - var result_xml = Blockly.Xml.domToText(event.oldXml); - chai.assert.equal(expected_xml, result_xml); - chai.assert.deepEqual(ids, event.ids); - chai.assert.equal(type, event.type); - } - function createSimpleTestBlock(workspace, opt_prototypeName) { // Disable events while constructing the block: this is a test of the // Blockly.Event constructors, not the block constructor.s @@ -71,84 +47,107 @@ suite('Events', function() { suite('Constructors', function() { test('Abstract', function() { var event = new Blockly.Events.Abstract(); - chai.assert.isUndefined(event.blockId); - chai.assert.isUndefined(event.workspaceId); - chai.assert.isUndefined(event.varId); - checkExactEventValues(event, {'group': '', 'recordUndo': true}); + assertEventEquals(event, undefined, undefined, undefined, { + 'recordUndo': true, + 'group': '' + }); }); test('UI event without block', function() { - Blockly.Events.setGroup('testGroup'); + var TEST_GROUP_ID = 'testGroup'; + Blockly.Events.setGroup(TEST_GROUP_ID); var event = new Blockly.Events.Ui(null, 'foo', 'bar', 'baz'); - checkExactEventValues(event, - { - 'blockId': null, - 'workspaceId': null, - 'type': 'ui', - 'oldValue': 'bar', - 'newValue': 'baz', - 'element': 'foo', - 'recordUndo': false, - 'group': 'testGroup' - }); + assertEventEquals(event, Blockly.Events.UI, null, null, { + 'element': 'foo', + 'oldValue': 'bar', + 'newValue': 'baz', + 'recordUndo': false, + 'group': TEST_GROUP_ID + }); }); suite('With simple blocks', function() { setup(function() { - this.FAKE_ID = 'hedgehog'; - sinon.stub(Blockly.utils, "genUid").returns(this.FAKE_ID); + this.TEST_BLOCK_ID = 'test_block_id'; + this.TEST_PARENT_ID = 'parent'; + // genUid is expected to be called either once or twice in this suite. + this.genUidStub = createGenUidStubWithReturns( + [this.TEST_BLOCK_ID, this.TEST_PARENT_ID]); this.block = createSimpleTestBlock(this.workspace); - sinon.restore(); }); - teardown(function() { + sinon.restore(); }); test('Block base', function() { var event = new Blockly.Events.BlockBase(this.block); - chai.assert.isUndefined(event.varId); - checkExactEventValues(event, + sinon.assert.calledOnce(this.genUidStub); + assertEventEquals(event, undefined, + this.workspace.id, this.TEST_BLOCK_ID, { - 'blockId': this.FAKE_ID, - 'workspaceId': this.workspace.id, + 'varId': undefined, + 'recordUndo': true, 'group': '', - 'recordUndo': true }); }); test('Create', function() { var event = new Blockly.Events.Create(this.block); - checkCreateEventValues(event, this.block, [this.FAKE_ID], 'create'); + sinon.assert.calledOnce(this.genUidStub); + assertEventEquals(event, Blockly.Events.CREATE, + this.workspace.id, this.TEST_BLOCK_ID, + { + 'recordUndo': true, + 'group': '', + }); }); test('Block create', function() { var event = new Blockly.Events.BlockCreate(this.block); - checkCreateEventValues(event, this.block, [this.FAKE_ID], 'create'); + sinon.assert.calledOnce(this.genUidStub); + assertEventEquals(event, Blockly.Events.CREATE, + this.workspace.id, this.TEST_BLOCK_ID, + { + 'recordUndo': true, + 'group': '', + }); }); test('Delete', function() { var event = new Blockly.Events.Delete(this.block); - checkDeleteEventValues(event, this.block, [this.FAKE_ID], 'delete'); + sinon.assert.calledOnce(this.genUidStub); + assertEventEquals(event, Blockly.Events.DELETE, + this.workspace.id, this.TEST_BLOCK_ID, + { + 'recordUndo': true, + 'group': '', + }); }); test('Block delete', function() { var event = new Blockly.Events.BlockDelete(this.block); - checkDeleteEventValues(event, this.block, [this.FAKE_ID], 'delete'); + sinon.assert.calledOnce(this.genUidStub); + assertEventEquals(event, Blockly.Events.DELETE, + this.workspace.id, this.TEST_BLOCK_ID, + { + 'recordUndo': true, + 'group': '', + }); }); test('UI event with block', function() { - Blockly.Events.setGroup('testGroup'); + var TEST_GROUP_ID = 'testGroup'; + Blockly.Events.setGroup(TEST_GROUP_ID); var event = new Blockly.Events.Ui(this.block, 'foo', 'bar', 'baz'); - checkExactEventValues(event, + sinon.assert.calledOnce(this.genUidStub); + assertEventEquals(event, Blockly.Events.UI, this.workspace.id, + this.TEST_BLOCK_ID, { - 'blockId': this.FAKE_ID, - 'workspaceId': this.workspace.id, - 'type': 'ui', + 'element': 'foo', 'oldValue': 'bar', 'newValue': 'baz', - 'element': 'foo', 'recordUndo': false, - 'group': 'testGroup' + 'group': TEST_GROUP_ID }); }); @@ -158,8 +157,15 @@ suite('Events', function() { this.block.xy_ = coordinate; var event = new Blockly.Events.Move(this.block); - checkExactEventValues(event, - {'oldCoordinate': coordinate, 'type': 'move'}); + sinon.assert.calledOnce(this.genUidStub); + assertEventEquals(event, Blockly.Events.MOVE, this.workspace.id, + this.TEST_BLOCK_ID, { + 'oldParentId': undefined, + 'oldInputName': undefined, + 'oldCoordinate': coordinate, + 'recordUndo': true, + 'group': '' + }); }); test('Block move by coordinate', function() { @@ -167,36 +173,54 @@ suite('Events', function() { this.block.xy_ = coordinate; var event = new Blockly.Events.BlockMove(this.block); - checkExactEventValues(event, - {'oldCoordinate': coordinate, 'type': 'move'}); + sinon.assert.calledOnce(this.genUidStub); + assertEventEquals(event, Blockly.Events.MOVE, this.workspace.id, + this.TEST_BLOCK_ID, { + 'oldParentId': undefined, + 'oldInputName': undefined, + 'oldCoordinate': coordinate, + 'recordUndo': true, + 'group': '' + }); }); suite('Move by parent', function() { setup(function() { - sinon.stub(Blockly.utils, "genUid").returns("parent"); this.parentBlock = createSimpleTestBlock(this.workspace); - sinon.restore(); this.block.parentBlock_ = this.parentBlock; this.block.xy_ = new Blockly.utils.Coordinate(3, 4); }); - teardown(function() { + // This needs to be cleared, otherwise workspace.dispose will fail. this.block.parentBlock_ = null; }); test('Move by parent', function() { - // Expect the oldParentId to be set but not the oldCoordinate to be set. var event = new Blockly.Events.Move(this.block); - checkExactEventValues(event, {'oldCoordinate': undefined, - 'oldParentId': 'parent', 'type': 'move'}); + sinon.assert.calledTwice(this.genUidStub); + assertEventEquals(event, Blockly.Events.MOVE, this.workspace.id, + this.TEST_BLOCK_ID, { + 'oldParentId': this.TEST_PARENT_ID, + 'oldInputName': undefined, + 'oldCoordinate': undefined, + 'recordUndo': true, + 'group': '' + }); }); test('Block move by parent', function() { - // Expect the oldParentId to be set but not the oldCoordinate to be set. var event = new Blockly.Events.BlockMove(this.block); - checkExactEventValues(event, {'oldCoordinate': undefined, - 'oldParentId': 'parent', 'type': 'move'}); + sinon.assert.calledTwice(this.genUidStub); + assertEventEquals(event, Blockly.Events.MOVE, this.workspace.id, + this.TEST_BLOCK_ID, + { + 'oldParentId': this.TEST_PARENT_ID, + 'oldInputName': undefined, + 'oldCoordinate': undefined, + 'recordUndo': true, + 'group': '' + }); }); }); }); @@ -204,21 +228,44 @@ suite('Events', function() { suite('With variable getter blocks', function() { setup(function() { - this.block = createSimpleTestBlock(this.workspace, 'field_variable_test_block'); + this.genUidStub = createGenUidStubWithReturns(this.TEST_BLOCK_ID); + this.block = + createSimpleTestBlock(this.workspace, 'field_variable_test_block'); + }); + teardown(function() { + sinon.restore(); }); test('Change', function() { - var event = - new Blockly.Events.Change(this.block, 'field', 'VAR', 'id1', 'id2'); - checkExactEventValues(event, {'element': 'field', 'name': 'VAR', - 'oldValue': 'id1', 'newValue': 'id2', 'type': 'change'}); + var event = new Blockly.Events.Change( + this.block, 'field', 'VAR', 'id1', 'id2'); + sinon.assert.calledOnce(this.genUidStub); + assertEventEquals(event, Blockly.Events.CHANGE, this.workspace.id, + this.TEST_BLOCK_ID, + { + 'element': 'field', + 'name': 'VAR', + 'oldValue': 'id1', + 'newValue': 'id2', + 'recordUndo': true, + 'group': '' + }); }); test('Block change', function() { var event = new Blockly.Events.BlockChange( this.block, 'field', 'VAR', 'id1', 'id2'); - checkExactEventValues(event, {'element': 'field', 'name': 'VAR', - 'oldValue': 'id1', 'newValue': 'id2', 'type': 'change'}); + sinon.assert.calledOnce(this.genUidStub); + assertEventEquals(event, Blockly.Events.CHANGE, this.workspace.id, + this.TEST_BLOCK_ID, + { + 'element': 'field', + 'name': 'VAR', + 'oldValue': 'id1', + 'newValue': 'id2', + 'recordUndo': true, + 'group': '' + }); }); }); }); @@ -247,27 +294,50 @@ suite('Events', function() { suite('Constructors', function() { test('Var base', function() { var event = new Blockly.Events.VarBase(this.variable); - chai.assert.isUndefined(event.blockId); - checkExactEventValues(event, {'varId': 'id1', - 'workspaceId': this.workspace.id, 'group': '', 'recordUndo': true}); + assertEventEquals(event, undefined, this.workspace.id, undefined, { + 'varId': 'id1', + 'recordUndo': true, + 'group': '' + }); }); test('Var create', function() { var event = new Blockly.Events.VarCreate(this.variable); - checkExactEventValues(event, {'varName': 'name1', 'varType': 'type1', - 'type': 'var_create'}); + assertEventEquals(event, Blockly.Events.VAR_CREATE, this.workspace.id, + undefined, + { + 'varId': 'id1', + 'varType': 'type1', + 'varName': 'name1', + 'recordUndo': true, + 'group': '' + }); }); test('Var delete', function() { var event = new Blockly.Events.VarDelete(this.variable); - checkExactEventValues(event, {'varName': 'name1', 'varType': 'type1', - 'varId':'id1', 'type': 'var_delete'}); + assertEventEquals(event, Blockly.Events.VAR_DELETE, this.workspace.id, + undefined, + { + 'varId': 'id1', + 'varType': 'type1', + 'varName': 'name1', + 'recordUndo': true, + 'group': '' + }); }); test('Var rename', function() { var event = new Blockly.Events.VarRename(this.variable, 'name2'); - checkExactEventValues(event, {'varId': 'id1', 'oldName': 'name1', - 'newName': 'name2', 'type': 'var_rename'}); + assertEventEquals(event, Blockly.Events.VAR_RENAME, this.workspace.id, + undefined, + { + 'varId': 'id1', + 'oldName': 'name1', + 'newName': 'name2', + 'recordUndo': true, + 'group': '' + }); }); }); @@ -378,7 +448,6 @@ suite('Events', function() { }); suite('Filters', function() { - function addMoveEvent(events, block, newX, newY) { events.push(new Blockly.Events.BlockMove(block)); block.xy_ = new Blockly.utils.Coordinate(newX, newY); @@ -556,93 +625,113 @@ suite('Events', function() { suite('Firing', function() { setup(function() { - createEventsFireStub(); + this.eventsStub = createEventsFireStub(); + this.changeListenerSpy = createFireChangeListenerSpy(this.workspace); }); teardown(function() { sinon.restore(); }); - test('Block dispose triggers BlockDelete', function() { + test('Block dispose triggers Delete', function() { try { var toolbox = document.getElementById('toolbox-categories'); var workspaceSvg = Blockly.inject('blocklyDiv', {toolbox: toolbox}); - Blockly.Events.fire.firedEvents_ = []; var block = workspaceSvg.newBlock(''); block.initSvg(); block.setCommentText('test comment'); + var expectedOldXml = Blockly.Xml.blockToDomWithXY(block); + var expectedId = block.id; - var event = new Blockly.Events.BlockDelete(block); - - workspaceSvg.clearUndo(); block.dispose(); - var firedEvents = workspaceSvg.undoStack_; - chai.assert.equal( - Blockly.Xml.domToText(firedEvents[0].oldXml), - Blockly.Xml.domToText(event.oldXml), - 'Delete event created by dispose'); + assertLastCallEventArgEquals( + this.eventsStub, Blockly.Events.DELETE, workspaceSvg.id, + expectedId, {oldXml: expectedOldXml}); } finally { workspaceSvg.dispose(); } }); test('New block new var', function() { + var TEST_BLOCK_ID = 'test_block_id'; + var TEST_GROUP_ID = 'test_group_id'; + var TEST_VAR_ID = 'test_var_id'; + var genUidStub = createGenUidStubWithReturns( + [TEST_BLOCK_ID, TEST_GROUP_ID, TEST_VAR_ID]); + var _ = this.workspace.newBlock('field_variable_test_block'); + var TEST_VAR_NAME = 'item'; // As defined in block's json. + // 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. - var stub = sinon.stub(Blockly.utils, "genUid"); - stub.onCall(0).returns('1'); - stub.onCall(1).returns('2'); - stub.onCall(2).returns('3'); - var _ = this.workspace.newBlock('field_variable_test_block'); + sinon.assert.calledThrice(genUidStub); - var firedEvents = this.workspace.undoStack_; - // Expect two events: varCreate and block create. - chai.assert.equal(2, firedEvents.length); + // Expect two events fired: varCreate and block create. + sinon.assert.calledTwice(this.eventsStub); + // Expect both events to trigger change listener. + sinon.assert.calledTwice(this.changeListenerSpy); + // Both events should be on undo stack + chai.assert.equal(this.workspace.undoStack_.length, 2, + 'Undo stack length'); - var event0 = firedEvents[0]; - var event1 = firedEvents[1]; - chai.assert.equal(event0.type, 'var_create'); - chai.assert.equal(event1.type, 'create'); + assertNthCallEventArgEquals( + this.changeListenerSpy, 0, Blockly.Events.VAR_CREATE, this.workspace.id, + undefined, {group: TEST_GROUP_ID, varId: TEST_VAR_ID, + varName: TEST_VAR_NAME}, 'varCreate:'); + assertNthCallEventArgEquals( + this.changeListenerSpy, 1, Blockly.Events.CREATE, this.workspace.id, + TEST_BLOCK_ID, {group: TEST_GROUP_ID}, 'block create:'); - // Expect the events to have the same group ID. - chai.assert.equal(event0.group, event1.group); - - // Expect the group ID to be the result of the second call to genUid. - chai.assert.equal(event0.group, '2'); - - // Expect the workspace to have a variable with ID '3'. - chai.assert.isNotNull(this.workspace.getVariableById('3')); - chai.assert.equal(event0.varId, '3'); + // Expect the workspace to have a variable with ID 'test_var_id'. + chai.assert.isNotNull(this.workspace.getVariableById(TEST_VAR_ID)); }); test('New block new var xml', function() { - // The sequence of events should be the same whether the block was created from - // XML or directly. + var TEST_GROUP_ID = 'test_group_id'; + var genUidStub = createGenUidStubWithReturns(TEST_GROUP_ID); var dom = Blockly.Xml.textToDom( '' + - ' ' + - ' name1' + + ' ' + + ' name1' + ' ' + ''); Blockly.Xml.domToWorkspace(dom, this.workspace); + var TEST_BLOCK_ID = 'test_block_id'; + var TEST_VAR_ID = 'test_var_id'; + var TEST_VAR_NAME = 'name1'; - var firedEvents = this.workspace.undoStack_; - // Expect two events: varCreate and block create. - chai.assert.equal(2, firedEvents.length); + // Expect one call to genUid: for the event group's id + sinon.assert.calledOnce(genUidStub); - var event0 = firedEvents[0]; - var event1 = firedEvents[1]; - chai.assert.equal(event0.type, 'var_create'); - chai.assert.equal(event1.type, 'create'); + // When block is created using domToWorkspace, 5 events are fired: + // 1. varCreate (events disabled) + // 2. varCreate + // 3. block create + // 4. move (no-op, is filtered out) + // 5. finished loading + sinon.assert.callCount(this.eventsStub, 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. + chai.assert.equal(2, this.workspace.undoStack_.length, + 'Undo stack length'); - // Expect the events to have the same group ID. - chai.assert.equal(event0.group, event1.group); + assertNthCallEventArgEquals( + this.changeListenerSpy, 0, Blockly.Events.VAR_CREATE, this.workspace.id, + undefined, {group: TEST_GROUP_ID, varId: TEST_VAR_ID, + varName: TEST_VAR_NAME}, 'varCreate:'); + assertNthCallEventArgEquals( + this.changeListenerSpy, 1, Blockly.Events.CREATE, this.workspace.id, + TEST_BLOCK_ID, {group: TEST_GROUP_ID}, 'block create:'); - // Expect the workspace to have a variable with ID 'id1'. - chai.assert.isNotNull(this.workspace.getVariableById('id1')); - chai.assert.equal(event0.varId, 'id1'); + // Finished loading event should not be part of event group. + assertNthCallEventArgEquals( + this.changeListenerSpy, 2, Blockly.Events.FINISHED_LOADING, this.workspace.id, + undefined, {group: ''}, 'finished loading:'); + + // Expect the workspace to have a variable with ID 'test_var_id'. + chai.assert.isNotNull(this.workspace.getVariableById(TEST_VAR_ID)); }); }); }); diff --git a/tests/mocha/field_variable_test.js b/tests/mocha/field_variable_test.js index c1d3207a8..b4776187b 100644 --- a/tests/mocha/field_variable_test.js +++ b/tests/mocha/field_variable_test.js @@ -45,9 +45,7 @@ suite('Variable Fields', function() { setup(function() { this.workspace = new Blockly.Workspace(); - - sinon.stub(Blockly.utils, 'genUid') - .returns(FAKE_ID); + createGenUidStubWithReturns(FAKE_ID); sinon.stub(Blockly.Variables, 'generateUniqueName') .returns(FAKE_VARIABLE_NAME); }); @@ -325,8 +323,6 @@ suite('Variable Fields', function() { }); teardown(function() { this.variableBlock.dispose(); - this.variableBlock = null; - this.variableField = null; delete Blockly.Blocks['field_variable_test_block']; }); test('Rename & Keep Old ID', function() { diff --git a/tests/mocha/gesture_test.js b/tests/mocha/gesture_test.js index 4372151bc..4ba2b591c 100644 --- a/tests/mocha/gesture_test.js +++ b/tests/mocha/gesture_test.js @@ -18,7 +18,6 @@ suite('Gesture', function() { }); teardown(function() { - this.e = null; this.workspace.dispose(); }); diff --git a/tests/mocha/test_helpers.js b/tests/mocha/test_helpers.js index 636d52d7e..f65c70606 100644 --- a/tests/mocha/test_helpers.js +++ b/tests/mocha/test_helpers.js @@ -39,13 +39,37 @@ function captureWarnings(innerFunc) { return msgs; } +/** + * 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. + * @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. + * @return {!SinonStub} The created stub. + */ +function createGenUidStubWithReturns(returnIds) { + var stub = sinon.stub(Blockly.utils, "genUid"); + if (Array.isArray(returnIds)) { + for (var i = 0; i < returnIds.length; i++) { + stub.onCall(i).returns(returnIds[i]); + } + } else { + stub.returns(returnIds); + } + return stub; +} /** * Creates stub for Blockly.Events.fire that fires events immediately instead of * with timeout. - * @return {sinon.stub} The created stub. + * @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()) { @@ -54,10 +78,87 @@ function createEventsFireStub() { Blockly.Events.FIRE_QUEUE_.push(event); Blockly.Events.fireNow_(); }); - stub.firedEvents_ = []; return stub; } +/** + * Creates spy for workspace fireChangeListener + * @param {!Blockly.Workspace} workspace The workspace to spy fireChangeListener + * calls on. + * @return {!SinonSpy} The created spy. + */ +function createFireChangeListenerSpy(workspace) { + return sinon.spy(workspace, 'fireChangeListener'); +} + +/** + * Asserts that the given event has the expected values. + * @param {!Blockly.Event.Abstract} event The event to check. + * @param {string} expectedType Expected type of event fired. + * @param {string} expectedWorkspaceId Expected workspace id of event fired. + * @param {string} expectedBlockId Expected block id of event fired. + * @param {!Object} expectedProperties Map of of additional expected + * properties to check on fired event. + * @param {string=} message Optional message to prepend assert messages. + * @private + */ +function assertEventEquals(event, expectedType, + expectedWorkspaceId, expectedBlockId, expectedProperties, message) { + var prependMessage = message ? message + ' ' : ''; + chai.assert.equal(event.type, expectedType, + prependMessage + 'Event fired type'); + chai.assert.equal(event.workspaceId, expectedWorkspaceId, + prependMessage + 'Event fired workspace id'); + chai.assert.equal(event.blockId, expectedBlockId, + prependMessage + 'Event fired block id'); + Object.keys(expectedProperties).map((key) => { + var value = event[key]; + var expectedValue = expectedProperties[key]; + if (key.endsWith('Xml')) { + value = Blockly.Xml.domToText(value); + expectedValue = Blockly.Xml.domToText(expectedValue); + } + chai.assert.equal(value, expectedValue, prependMessage + 'Event fired ' + key); + }); +} + +/** + * 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. + * @param {!SinonSpy} spy The spy to use. + * @param {string} expectedType Expected type of event fired. + * @param {string} expectedWorkspaceId Expected workspace id of event fired. + * @param {string} expectedBlockId Expected block id of event fired. + * @param {!Object} expectedProperties Map of of expected properties + * to check on fired event. + * @param {string=} message Optional message to prepend assert messages. + */ +function assertLastCallEventArgEquals(spy, expectedType, + expectedWorkspaceId, expectedBlockId, expectedProperties, message) { + var event = spy.lastCall.firstArg; + assertEventEquals(event, expectedType, expectedWorkspaceId, expectedBlockId, + expectedProperties, message); +} + +/** + * 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. + * @param {!SinonSpy} spy The spy to use. + * @param {number} n Which call to check. + * @param {string} expectedType Expected type of event fired. + * @param {string} expectedWorkspaceId Expected workspace id of event fired. + * @param {string} expectedBlockId Expected block id of event fired. + * @param {Object} expectedProperties Map of of expected properties + * to check on fired event. + * @param {string=} message Optional message to prepend assert messages. + */ +function assertNthCallEventArgEquals(spy, n, expectedType, + expectedWorkspaceId, expectedBlockId, expectedProperties, message) { + var event = spy.getCall(n).firstArg; + assertEventEquals(event, expectedType, expectedWorkspaceId, expectedBlockId, + expectedProperties, message); +} + function defineStackBlock() { Blockly.defineBlocksWithJsonArray([{ "type": "stack_block", diff --git a/tests/mocha/trashcan_test.js b/tests/mocha/trashcan_test.js index 388e33405..315ebc66e 100644 --- a/tests/mocha/trashcan_test.js +++ b/tests/mocha/trashcan_test.js @@ -5,52 +5,46 @@ */ suite("Trashcan", function() { - var workspace = { - addChangeListener: function(func) { - this.listener = func; - }, - triggerListener: function(event) { - this.listener(event); - }, - options: { - maxTrashcanContents: Infinity - } - }; - var themeManager = new Blockly.ThemeManager(workspace, Blockly.Themes.Classic); - workspace.getThemeManager = function() { - return themeManager; - }; - function sendDeleteEvent(xmlString) { + function fireDeleteEvent(workspace, xmlString) { var xml = Blockly.Xml.textToDom( '' + xmlString + ''); xml = xml.children[0]; - var event = { - type: Blockly.Events.BLOCK_DELETE, - oldXml: xml - }; - workspace.triggerListener(event); + var event = new Blockly.Events.Delete(null); + event.oldXml = xml; + event.workspaceId = workspace.id; + Blockly.Events.fire(event); + } + function fireNonDeleteEvent(workspace, oldXml) { + var event = new Blockly.Events.Abstract(); + event.type = 'dummy_type'; + event.workspaceId = workspace.id; + if (oldXml) { + event.oldXml = oldXml; + } + Blockly.Events.fire(/** @type {Blockly.Events.Abstract} */ event); } setup(function() { - this.trashcan = new Blockly.Trashcan(workspace); - this.setLidStub = sinon.stub(this.trashcan, 'setLidAngle_'); + this.eventsStub = createEventsFireStub(); + var options = new Blockly.Options( + {'trashcan': true, 'maxTrashcanContents': Infinity}); + this.workspace = new Blockly.WorkspaceSvg(options); + this.trashcan = new Blockly.Trashcan(this.workspace); + // Stub the trashcan dom. + this.trashcan.svgLid_ = sinon.createStubInstance(SVGElement); }); teardown(function() { - this.setLidStub.restore(); - this.trashcan = null; + sinon.restore(); }); suite("Events", function() { test("Delete", function() { - sendDeleteEvent(''); + fireDeleteEvent(this.workspace, ''); chai.assert.equal(this.trashcan.contents_.length, 1); }); test("Non-Delete", function() { - var event = { - type: 'dummy_type' - }; - workspace.triggerListener(event); + fireNonDeleteEvent(this.workspace); chai.assert.equal(this.trashcan.contents_.length, 0); }); test("Non-Delete w/ oldXml", function() { @@ -60,58 +54,70 @@ suite("Trashcan", function() { '' ); xml = xml.children[0]; - var event = { - type: 'dummy_type', - oldXml: xml - }; - workspace.triggerListener(event); + fireNonDeleteEvent(this.workspace, xml); chai.assert.equal(this.trashcan.contents_.length, 0); }); test("Shadow Delete", function() { - sendDeleteEvent(''); + fireDeleteEvent(this.workspace, ''); chai.assert.equal(this.trashcan.contents_.length, 0); }); + test("Click without contents - fires no events", function() { + this.trashcan.click(); + var lastFireCall = this.eventsStub.lastCall; + chai.assert.notExists(lastFireCall); + }); + test("Click with contents - fires trashcanOpen", function() { + fireDeleteEvent(this.workspace, ''); + chai.assert.equal(this.trashcan.contents_.length, 1); + // Stub flyout interaction. + var showFlyoutStub = sinon.stub(this.trashcan.flyout, "show"); + this.trashcan.click(); + assertLastCallEventArgEquals( + this.eventsStub, Blockly.Events.UI, this.workspace.id, undefined, + {element: 'trashcanOpen', oldValue: null, newValue: true}); + sinon.assert.calledOnce(showFlyoutStub); + }); }); suite("Unique Contents", function() { test("Simple", function() { - sendDeleteEvent(''); - sendDeleteEvent(''); + fireDeleteEvent(this.workspace, ''); + fireDeleteEvent(this.workspace, ''); chai.assert.equal(this.trashcan.contents_.length, 1); }); test("Different Coords", function() { - sendDeleteEvent(''); - sendDeleteEvent(''); + fireDeleteEvent(this.workspace, ''); + fireDeleteEvent(this.workspace, ''); chai.assert.equal(this.trashcan.contents_.length, 1); }); test("Different IDs", function() { - sendDeleteEvent(''); - sendDeleteEvent(''); + fireDeleteEvent(this.workspace, ''); + fireDeleteEvent(this.workspace, ''); chai.assert.equal(this.trashcan.contents_.length, 1); }); test("No Disabled - Disabled True", function() { - sendDeleteEvent(''); - sendDeleteEvent(''); + fireDeleteEvent(this.workspace, ''); + fireDeleteEvent(this.workspace, ''); // Disabled tags get removed because disabled blocks aren't allowed to // be dragged from flyouts. See #2239 and #3243. chai.assert.equal(this.trashcan.contents_.length, 1); }); test("No Editable - Editable False", function() { - sendDeleteEvent(''); - sendDeleteEvent(''); + fireDeleteEvent(this.workspace, ''); + fireDeleteEvent(this.workspace, ''); chai.assert.equal(this.trashcan.contents_.length, 2); }); test("No Movable - Movable False", function() { - sendDeleteEvent(''); - sendDeleteEvent(''); + fireDeleteEvent(this.workspace, ''); + fireDeleteEvent(this.workspace, ''); chai.assert.equal(this.trashcan.contents_.length, 2); }); test("Different Field Values", function() { - sendDeleteEvent( + fireDeleteEvent(this.workspace, '' + ' dummy_value1' + '' ); - sendDeleteEvent( + fireDeleteEvent(this.workspace, '' + ' dummy_value2' + '' @@ -119,8 +125,8 @@ suite("Trashcan", function() { chai.assert.equal(this.trashcan.contents_.length, 2); }); test("No Values - Values", function() { - sendDeleteEvent(''); - sendDeleteEvent( + fireDeleteEvent(this.workspace, ''); + fireDeleteEvent(this.workspace, '' + ' ' + ' ' + @@ -130,14 +136,14 @@ suite("Trashcan", function() { chai.assert.equal(this.trashcan.contents_.length, 2); }); test("Different Value Blocks", function() { - sendDeleteEvent( + fireDeleteEvent(this.workspace, '' + ' ' + ' ' + ' ' + '' ); - sendDeleteEvent( + fireDeleteEvent(this.workspace, '' + ' ' + ' ' + @@ -147,8 +153,8 @@ suite("Trashcan", function() { chai.assert.equal(this.trashcan.contents_.length, 2); }); test("No Statements - Statements", function() { - sendDeleteEvent(''); - sendDeleteEvent( + fireDeleteEvent(this.workspace, ''); + fireDeleteEvent(this.workspace, '' + ' ' + ' ' + @@ -158,14 +164,14 @@ suite("Trashcan", function() { chai.assert.equal(this.trashcan.contents_.length, 2); }); test("Different Statement Blocks", function() { - sendDeleteEvent( + fireDeleteEvent(this.workspace, '' + ' ' + ' ' + ' ' + '' ); - sendDeleteEvent( + fireDeleteEvent(this.workspace, '' + ' ' + ' ' + @@ -175,8 +181,8 @@ suite("Trashcan", function() { chai.assert.equal(this.trashcan.contents_.length, 2); }); test("No Next - Next", function() { - sendDeleteEvent(''); - sendDeleteEvent( + fireDeleteEvent(this.workspace, ''); + fireDeleteEvent(this.workspace, '' + ' ' + ' ' + @@ -186,14 +192,14 @@ suite("Trashcan", function() { chai.assert.equal(this.trashcan.contents_.length, 2); }); test("Different Next Blocks", function() { - sendDeleteEvent( + fireDeleteEvent(this.workspace, '' + ' ' + ' ' + ' ' + '' ); - sendDeleteEvent( + fireDeleteEvent(this.workspace, '' + ' ' + ' ' + @@ -203,8 +209,8 @@ suite("Trashcan", function() { chai.assert.equal(this.trashcan.contents_.length, 2); }); test("No Comment - Comment", function() { - sendDeleteEvent(''); - sendDeleteEvent( + fireDeleteEvent(this.workspace, ''); + fireDeleteEvent(this.workspace, '' + ' comment_text' + '' @@ -212,12 +218,12 @@ suite("Trashcan", function() { chai.assert.equal(this.trashcan.contents_.length, 2); }); test("Different Comment Text", function() { - sendDeleteEvent( + fireDeleteEvent(this.workspace, '' + ' comment_text1' + '' ); - sendDeleteEvent( + fireDeleteEvent(this.workspace, '' + ' comment_text2' + '' @@ -225,12 +231,12 @@ suite("Trashcan", function() { chai.assert.equal(this.trashcan.contents_.length, 2); }); test("Different Comment Size", function() { - sendDeleteEvent( + fireDeleteEvent(this.workspace, '' + ' comment_text' + '' ); - sendDeleteEvent( + fireDeleteEvent(this.workspace, '' + ' comment_text' + '' @@ -239,12 +245,12 @@ suite("Trashcan", function() { chai.assert.equal(this.trashcan.contents_.length, 1); }); test("Different Comment Pinned", function() { - sendDeleteEvent( + fireDeleteEvent(this.workspace, '' + ' comment_text' + '' ); - sendDeleteEvent( + fireDeleteEvent(this.workspace, '' + ' comment_text' + '' @@ -253,8 +259,8 @@ suite("Trashcan", function() { chai.assert.equal(this.trashcan.contents_.length, 1); }); test("No Mutator - Mutator", function() { - sendDeleteEvent(''); - sendDeleteEvent( + fireDeleteEvent(this.workspace, ''); + fireDeleteEvent(this.workspace, '' + ' ' + '' @@ -262,12 +268,12 @@ suite("Trashcan", function() { chai.assert.equal(this.trashcan.contents_.length, 2); }); test("Different Mutator", function() { - sendDeleteEvent( + fireDeleteEvent(this.workspace, '' + ' ' + '' ); - sendDeleteEvent( + fireDeleteEvent(this.workspace, '' + ' ' + '' @@ -277,24 +283,24 @@ suite("Trashcan", function() { }); suite("Max Contents", function() { test("Max 0", function() { - workspace.options.maxTrashcanContents = 0; - sendDeleteEvent( + this.workspace.options.maxTrashcanContents = 0; + fireDeleteEvent(this.workspace, '' ); chai.assert.equal(this.trashcan.contents_.length, 0); - workspace.options.maxTrashcanContents = Infinity; + this.workspace.options.maxTrashcanContents = Infinity; }); test("Last In First Out", function() { - workspace.options.maxTrashcanContents = 1; - sendDeleteEvent(''); - sendDeleteEvent(''); + this.workspace.options.maxTrashcanContents = 1; + fireDeleteEvent(this.workspace, ''); + fireDeleteEvent(this.workspace, ''); chai.assert.equal(this.trashcan.contents_.length, 1); chai.assert.equal( Blockly.Xml.textToDom(this.trashcan.contents_[0]) .getAttribute('type'), 'dummy_type2' ); - workspace.options.maxTrashcanContents = Infinity; + this.workspace.options.maxTrashcanContents = Infinity; }); }); }); diff --git a/tests/mocha/variable_map_test.js b/tests/mocha/variable_map_test.js index c68c96984..6c7fa7bf8 100644 --- a/tests/mocha/variable_map_test.js +++ b/tests/mocha/variable_map_test.js @@ -70,13 +70,13 @@ suite('Variable Map', function() { }); test('Null id', function() { - sinon.stub(Blockly.utils, "genUid").returns('1'); + createGenUidStubWithReturns('1'); this.variableMap.createVariable('name1', 'type1', null); assertVariableValues(this.variableMap, 'name1', 'type1', '1'); }); test('Undefined id', function() { - sinon.stub(Blockly.utils, "genUid").returns('1'); + createGenUidStubWithReturns('1'); this.variableMap.createVariable('name1', 'type1', undefined); assertVariableValues(this.variableMap, 'name1', 'type1', '1'); }); diff --git a/tests/mocha/xml_test.js b/tests/mocha/xml_test.js index 0acab1b17..7d1b1841d 100644 --- a/tests/mocha/xml_test.js +++ b/tests/mocha/xml_test.js @@ -299,7 +299,7 @@ suite('XML', function() { assertVariableDomField(resultFieldDom, 'VAR', 'string', 'id1', 'name1'); }); test('Variable Default Case', function() { - sinon.stub(Blockly.utils, 'genUid').returns('1'); + createGenUidStubWithReturns('1'); this.workspace.createVariable('name1'); Blockly.Events.disable(); @@ -409,7 +409,7 @@ suite('XML', function() { this.workspace.dispose(); }); test('One Variable', function() { - sinon.stub(Blockly.utils, 'genUid').returns('1'); + createGenUidStubWithReturns('1'); this.workspace.createVariable('name1'); var resultDom = Blockly.Xml.variablesToDom(this.workspace.getAllVariables()); @@ -685,7 +685,7 @@ suite('XML', function() { this.workspace.dispose(); }); test('Backwards compatibility', function() { - sinon.stub(Blockly.utils, 'genUid').returns('1'); + createGenUidStubWithReturns('1'); var dom = Blockly.Xml.textToDom( '' + ' ' +