From 332ff10672dc2d0d2de6a9cfb56c599ed377381f Mon Sep 17 00:00:00 2001 From: Monica Kozbial Date: Wed, 12 Aug 2020 13:27:37 -0700 Subject: [PATCH] Test gesture triggered events and event assertion refactor (#4155) * Refactor event assertion helpers and add assertions to gesture test. --- tests/mocha/.eslintrc.json | 3 +- tests/mocha/comment_test.js | 32 +++---- tests/mocha/event_test.js | 44 ++++++---- tests/mocha/gesture_test.js | 15 +++- tests/mocha/test_helpers.js | 166 +++++++++++++++++++++++++++++------ tests/mocha/theme_test.js | 6 +- tests/mocha/trashcan_test.js | 8 +- 7 files changed, 200 insertions(+), 74 deletions(-) diff --git a/tests/mocha/.eslintrc.json b/tests/mocha/.eslintrc.json index d111f6e1e..cbd03a1ff 100644 --- a/tests/mocha/.eslintrc.json +++ b/tests/mocha/.eslintrc.json @@ -9,7 +9,8 @@ "sinon": false, "assertArrayEquals": true, "assertEventEquals": true, - "assertLastCallEventArgEquals": true, + "assertEventFired": true, + "assertEventNotFired": true, "assertNthCallEventArgEquals": true, "assertVariableValues": true, "captureWarnings": true, diff --git a/tests/mocha/comment_test.js b/tests/mocha/comment_test.js index 53215f048..e026fdfd4 100644 --- a/tests/mocha/comment_test.js +++ b/tests/mocha/comment_test.js @@ -49,9 +49,10 @@ suite('Comments', function() { this.comment.setVisible(true); chai.assert.isTrue(this.comment.isVisible()); assertEditable(this.comment); - assertLastCallEventArgEquals( - this.eventsFireStub, Blockly.Events.UI, this.workspace.id, this.block.id, - {element: 'commentOpen', oldValue: false, newValue: true}); + assertEventFired( + this.eventsFireStub, Blockly.Events.Ui, + {element: 'commentOpen', oldValue: false, newValue: true}, + this.workspace.id, this.block.id); }); test('Not Editable', function() { sinon.stub(this.block, 'isEditable').returns(false); @@ -59,9 +60,10 @@ suite('Comments', function() { this.comment.setVisible(true); chai.assert.isTrue(this.comment.isVisible()); assertNotEditable(this.comment); - assertLastCallEventArgEquals( - this.eventsFireStub, Blockly.Events.UI, this.workspace.id, this.block.id, - {element: 'commentOpen', oldValue: false, newValue: true}); + assertEventFired( + this.eventsFireStub, Blockly.Events.Ui, + {element: 'commentOpen', oldValue: false, newValue: true}, + this.workspace.id, this.block.id); }); test('Editable -> Not Editable', function() { this.comment.setVisible(true); @@ -69,12 +71,11 @@ suite('Comments', function() { this.comment.updateEditable(); chai.assert.isTrue(this.comment.isVisible()); - assertNotEditable(this.comment);assertLastCallEventArgEquals( - this.eventsFireStub, Blockly.Events.UI, this.workspace.id, this.block.id, - {element: 'commentOpen', oldValue: false, newValue: true}); - assertLastCallEventArgEquals( - this.eventsFireStub, Blockly.Events.UI, this.workspace.id, this.block.id, - {element: 'commentOpen', oldValue: false, newValue: true}); + assertNotEditable(this.comment); + assertEventFired( + this.eventsFireStub, Blockly.Events.Ui, + {element: 'commentOpen', oldValue: false, newValue: true}, + this.workspace.id, this.block.id); }); test('Not Editable -> Editable', function() { var editableStub = sinon.stub(this.block, 'isEditable').returns(false); @@ -84,9 +85,10 @@ suite('Comments', function() { this.comment.updateEditable(); chai.assert.isTrue(this.comment.isVisible()); assertEditable(this.comment); - assertLastCallEventArgEquals( - this.eventsFireStub, Blockly.Events.UI, this.workspace.id, this.block.id, - {element: 'commentOpen', oldValue: false, newValue: true}); + assertEventFired( + this.eventsFireStub, Blockly.Events.Ui, + {element: 'commentOpen', oldValue: false, newValue: true}, + this.workspace.id, this.block.id); }); }); suite('Set/Get Bubble Size', function() { diff --git a/tests/mocha/event_test.js b/tests/mocha/event_test.js index 4e7228951..7645303b2 100644 --- a/tests/mocha/event_test.js +++ b/tests/mocha/event_test.js @@ -624,7 +624,6 @@ suite('Events', 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']); @@ -635,6 +634,11 @@ suite('Events', function() { var expectedOldXml = Blockly.Xml.blockToDomWithXY(block); var expectedId = block.id; + // Run all queued events. + this.clock.runAll(); + + this.eventsFireSpy.resetHistory(); + var changeListenerSpy = createFireChangeListenerSpy(workspaceSvg); block.dispose(); // Run all queued events. @@ -644,12 +648,14 @@ suite('Events', function() { // the event group's ID for creating block. sinon.assert.calledTwice(genUidStub); - assertLastCallEventArgEquals( - this.eventsFireSpy, Blockly.Events.DELETE, workspaceSvg.id, - expectedId, {oldXml: expectedOldXml, group: ''}); - assertLastCallEventArgEquals( - changeListenerSpy, Blockly.Events.DELETE, workspaceSvg.id, - expectedId, {oldXml: expectedOldXml, group: ''}); + assertNthCallEventArgEquals( + this.eventsFireSpy, 0, Blockly.Events.Delete, + {oldXml: expectedOldXml, group: ''}, + workspaceSvg.id, expectedId); + assertNthCallEventArgEquals( + changeListenerSpy, 0, Blockly.Events.Delete, + {oldXml: expectedOldXml, group: ''}, + workspaceSvg.id, expectedId); // Expect the workspace to not have a variable with ID 'test_block_id'. chai.assert.isNull(this.workspace.getVariableById(TEST_BLOCK_ID)); @@ -683,12 +689,12 @@ suite('Events', function() { 'Undo stack length'); 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:'); + this.changeListenerSpy, 0, Blockly.Events.VarCreate, + {group: TEST_GROUP_ID, varId: TEST_VAR_ID, varName: TEST_VAR_NAME}, + this.workspace.id, undefined); assertNthCallEventArgEquals( - this.changeListenerSpy, 1, Blockly.Events.CREATE, this.workspace.id, - TEST_BLOCK_ID, {group: TEST_GROUP_ID}, 'block create:'); + this.changeListenerSpy, 1, Blockly.Events.Create, + {group: TEST_GROUP_ID}, this.workspace.id, TEST_BLOCK_ID); // Expect the workspace to have a variable with ID 'test_var_id'. chai.assert.isNotNull(this.workspace.getVariableById(TEST_VAR_ID)); @@ -728,17 +734,17 @@ suite('Events', function() { 'Undo stack length'); 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:'); + this.changeListenerSpy, 0, Blockly.Events.VarCreate, + {group: TEST_GROUP_ID, varId: TEST_VAR_ID, varName: TEST_VAR_NAME}, + this.workspace.id, undefined); assertNthCallEventArgEquals( - this.changeListenerSpy, 1, Blockly.Events.CREATE, this.workspace.id, - TEST_BLOCK_ID, {group: TEST_GROUP_ID}, 'block create:'); + this.changeListenerSpy, 1, Blockly.Events.Create, + {group: TEST_GROUP_ID}, this.workspace.id, TEST_BLOCK_ID); // 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:'); + this.changeListenerSpy, 2, Blockly.Events.FinishedLoading, + {group: ''}, this.workspace.id, undefined); // 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/gesture_test.js b/tests/mocha/gesture_test.js index 4b37f4f2a..82d0c943f 100644 --- a/tests/mocha/gesture_test.js +++ b/tests/mocha/gesture_test.js @@ -11,12 +11,13 @@ 'use strict'; suite('Gesture', function() { - function testGestureIsFieldClick(block, isFieldClick){ + function testGestureIsFieldClick(block, isFieldClick, eventsFireStub){ var field = block.getField('NAME'); var eventTarget = field.getClickTarget_(); chai.assert.exists(eventTarget, 'Precondition: missing click target for field'); + eventsFireStub.resetHistory(); dispatchPointerEvent(eventTarget, 'pointerdown'); var fieldWorkspace = field.sourceBlock_.workspace; @@ -31,6 +32,12 @@ suite('Gesture', function() { sinon.assert.called(isFieldClickSpy); chai.assert.isTrue(isFieldClickSpy.alwaysReturned(isFieldClick)); + + + assertEventFired(eventsFireStub, Blockly.Events.Ui, + {element: 'selected', oldValue: null, newValue: block.id}, + fieldWorkspace.id, null); + assertEventNotFired(eventsFireStub, Blockly.Events.Ui, {element: 'click'}); } function getTopFlyoutBlock(flyout) { @@ -61,7 +68,7 @@ suite('Gesture', function() { block.initSvg(); block.render(); - testGestureIsFieldClick(block, true); + testGestureIsFieldClick(block, true, this.eventsFireStub); }); test('Field click - Auto close flyout', function() { @@ -71,7 +78,7 @@ suite('Gesture', function() { flyout.autoClose = true; var block = getTopFlyoutBlock(flyout); - testGestureIsFieldClick(block, false); + testGestureIsFieldClick(block, false, this.eventsFireStub); }); test('Field click - Always open flyout', function() { @@ -81,7 +88,7 @@ suite('Gesture', function() { flyout.autoClose = false; var block = getTopFlyoutBlock(flyout); - testGestureIsFieldClick(block, true); + testGestureIsFieldClick(block, true, this.eventsFireStub); }); test('Shift click in accessibility mode - moves the cursor', function() { diff --git a/tests/mocha/test_helpers.js b/tests/mocha/test_helpers.js index 571567068..366d97d02 100644 --- a/tests/mocha/test_helpers.js +++ b/tests/mocha/test_helpers.js @@ -175,6 +175,52 @@ function createFireChangeListenerSpy(workspace) { return sinon.spy(workspace, 'fireChangeListener'); } +/** + * Asserts whether the given xml property has the expected property. + * @param {!Node} xmlValue The xml value to check. + * @param {!Node|string} expectedValue The expected value. + * @param {string=} message Optional message to use in assert message. + * @private + */ +function assertXmlPropertyEqual_(xmlValue, expectedValue, message) { + var value = Blockly.Xml.domToText(xmlValue); + if (expectedValue instanceof Node) { + expectedValue = Blockly.Xml.domToText(expectedValue); + } + chai.assert.equal(value, expectedValue, message); +} + +/** + * Asserts that the given object has the expected xml properties. + * @param {Object} obj The object to check. + * @param {Object} expectedXmlProperties The expected xml + * properties. + * @private + */ +function assertXmlProperties_(obj, expectedXmlProperties) { + Object.keys(expectedXmlProperties).map((key) => { + var value = obj[key]; + var expectedValue = expectedXmlProperties[key]; + if (expectedValue === undefined) { + chai.assert.isUndefined(value, + 'Expected ' + key + ' property to be undefined'); + return; + } + chai.assert.exists(value, 'Expected ' + key + ' property to exist'); + assertXmlPropertyEqual_(value, expectedValue, 'Checking property ' + key); + }); +} + +/** + * Whether given key indicates that the property is xml. + * @param {string} key The key to check. + * @return {boolean} Whether the given key is for xml property. + * @private + */ +function isXmlProperty_(key) { + return key.toLowerCase().endsWith('xml'); +} + /** * Asserts that the given event has the expected values. * @param {!Blockly.Event.Abstract} event The event to check. @@ -184,44 +230,100 @@ function createFireChangeListenerSpy(workspace) { * @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 + ' ' : ''; + prependMessage += 'Event fired '; chai.assert.equal(event.type, expectedType, - prependMessage + 'Event fired type'); + prependMessage + 'type'); chai.assert.equal(event.workspaceId, expectedWorkspaceId, - prependMessage + 'Event fired workspace id'); + prependMessage + 'workspace id'); chai.assert.equal(event.blockId, expectedBlockId, - prependMessage + 'Event fired block id'); + prependMessage + '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); + if (expectedValue === undefined) { + chai.assert.isUndefined(value, prependMessage + key); + return; + } + chai.assert.exists(value, prependMessage + key); + if (isXmlProperty_(key)) { + assertXmlPropertyEqual_(value, expectedValue, + prependMessage + key); + } else { + chai.assert.equal(value, expectedValue, + prependMessage + key); } - 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. + * Asserts that an event with the given values was fired. + * @param {!SinonSpy|!SinonSpyCall} spy The spy or spy call to use. + * @param {function(new:Blockly.Events.Abstract)} instanceType Expected instance + * type 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. + * @param {string} expectedWorkspaceId Expected workspace id of event fired. + * @param {?string=} expectedBlockId Expected block id of event fired. */ -function assertLastCallEventArgEquals(spy, expectedType, - expectedWorkspaceId, expectedBlockId, expectedProperties, message) { - var event = spy.lastCall.firstArg; - assertEventEquals(event, expectedType, expectedWorkspaceId, expectedBlockId, - expectedProperties, message); +function assertEventFired(spy, instanceType, expectedProperties, + expectedWorkspaceId, expectedBlockId) { + expectedProperties = Object.assign({ + type: instanceType.prototype.type, + workspaceId: expectedWorkspaceId, + blockId: expectedBlockId, + }, expectedProperties); + var expectedEvent = + sinon.match.instanceOf(instanceType).and(sinon.match(expectedProperties)); + sinon.assert.calledWith(spy, expectedEvent); +} + +/** + * Asserts that an event with the given values was not fired. + * @param {!SpyCall} spy The spy to use. + * @param {function(new:Blockly.Events.Abstract)} instanceType Expected instance + * type of event fired. + * @param {!Object} expectedProperties Map of of expected properties + * to check on fired event. + * @param {string=} expectedWorkspaceId Expected workspace id of event fired. + * @param {?string=} expectedBlockId Expected block id of event fired. + */ +function assertEventNotFired(spy, instanceType, expectedProperties, + expectedWorkspaceId, expectedBlockId) { + expectedProperties.type = instanceType.prototype.type; + if (expectedWorkspaceId !== undefined) { + expectedProperties.workspaceId = expectedWorkspaceId; + } + if (expectedBlockId !== undefined) { + expectedProperties.blockId = expectedBlockId; + } + var expectedEvent = + sinon.match.instanceOf(instanceType).and(sinon.match(expectedProperties)); + sinon.assert.neverCalledWith(spy, expectedEvent); +} + +/** + * Filters out xml properties from given object based on key. + * @param {Object} properties The properties to filter. + * @return {[Object, Object]} A list containing split non + * xml properties and xml properties. + * @private + */ +function splitByXmlProperties_(properties) { + var xmlProperties = {}; + var nonXmlProperties = {}; + Object.keys(properties).forEach((key) => { + if (isXmlProperty_(key)) { + xmlProperties[key] = properties[key]; + return false; + } else { + nonXmlProperties[key] = properties[key]; + } + }); + return [nonXmlProperties, xmlProperties]; } /** @@ -229,18 +331,24 @@ function assertLastCallEventArgEquals(spy, expectedType, * 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 {function(new:Blockly.Events.Abstract)} instanceType Expected instance + * type 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. + * @param {string} expectedWorkspaceId Expected workspace id of event fired. + * @param {?string=} expectedBlockId Expected block id of event fired. */ -function assertNthCallEventArgEquals(spy, n, expectedType, - expectedWorkspaceId, expectedBlockId, expectedProperties, message) { - var event = spy.getCall(n).firstArg; - assertEventEquals(event, expectedType, expectedWorkspaceId, expectedBlockId, - expectedProperties, message); +function assertNthCallEventArgEquals(spy, n, instanceType, expectedProperties, + expectedWorkspaceId, expectedBlockId) { + var nthCall = spy.getCall(n); + var splitProperties = splitByXmlProperties_(expectedProperties); + var nonXmlProperties = splitProperties[0]; + var xmlProperties = splitProperties[1]; + + assertEventFired(nthCall, instanceType, nonXmlProperties, expectedWorkspaceId, + expectedBlockId); + var eventArg = nthCall.firstArg; + assertXmlProperties_(eventArg, xmlProperties); } function defineStackBlock() { diff --git a/tests/mocha/theme_test.js b/tests/mocha/theme_test.js index 240032458..c81e5048c 100644 --- a/tests/mocha/theme_test.js +++ b/tests/mocha/theme_test.js @@ -149,9 +149,9 @@ suite('Theme', function() { // Checks that the toolbox refreshed method was called sinon.assert.calledOnce(refreshToolboxSelectionStub); - assertLastCallEventArgEquals( - this.eventsFireStub, Blockly.Events.UI, workspace.id, - null, {element: 'theme'}); + assertEventFired( + this.eventsFireStub, Blockly.Events.Ui, {element: 'theme'}, + workspace.id, null); } finally { workspaceTeardown.call(this, workspace); undefineThemeTestBlocks(); diff --git a/tests/mocha/trashcan_test.js b/tests/mocha/trashcan_test.js index 1ffcf5bb4..458d21946 100644 --- a/tests/mocha/trashcan_test.js +++ b/tests/mocha/trashcan_test.js @@ -72,9 +72,11 @@ suite("Trashcan", function() { // Stub flyout interaction. var showFlyoutStub = sinon.stub(this.trashcan.flyout, "show"); this.trashcan.click(); - assertLastCallEventArgEquals( - this.eventsFireStub, Blockly.Events.UI, this.workspace.id, undefined, - {element: 'trashcanOpen', oldValue: null, newValue: true}); + + assertEventFired( + this.eventsFireStub, Blockly.Events.Ui, + {element: 'trashcanOpen', oldValue: null, newValue: true}, + this.workspace.id, null); sinon.assert.calledOnce(showFlyoutStub); }); });