Refactor shared (#4066)

* Refactoring event helpers.

* Fix concurrent test failure.
This commit is contained in:
Monica Kozbial
2020-07-24 14:49:39 -07:00
committed by GitHub
parent 4d8da1cda2
commit 3868db3221
9 changed files with 452 additions and 258 deletions

View File

@@ -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,

View File

@@ -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);

View File

@@ -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(
'<xml xmlns="https://developers.google.com/blockly/xml">' +
' <block type="field_variable_test_block" id="block1">' +
' <field name="VAR" id="id1">name1</field>' +
' <block type="field_variable_test_block" id="test_block_id">' +
' <field name="VAR" id="test_var_id">name1</field>' +
' </block>' +
'</xml>');
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));
});
});
});

View File

@@ -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() {

View File

@@ -18,7 +18,6 @@ suite('Gesture', function() {
});
teardown(function() {
this.e = null;
this.workspace.dispose();
});

View File

@@ -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<string>} 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<string, *>} 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<string, *>} 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<string, *>} 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",

View File

@@ -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(
'<xml xmlns="https://developers.google.com/blockly/xml">' +
xmlString + '</xml>');
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('<block type="dummy_type"/>');
fireDeleteEvent(this.workspace, '<block type="dummy_type"/>');
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 = 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('<shadow type="dummy_type"/>');
fireDeleteEvent(this.workspace, '<shadow type="dummy_type"/>');
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, '<block type="dummy_type"/>');
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('<block type="dummy_type"/>');
sendDeleteEvent('<block type="dummy_type"/>');
fireDeleteEvent(this.workspace, '<block type="dummy_type"/>');
fireDeleteEvent(this.workspace, '<block type="dummy_type"/>');
chai.assert.equal(this.trashcan.contents_.length, 1);
});
test("Different Coords", function() {
sendDeleteEvent('<block type="dummy_type" x="10" y="10"/>');
sendDeleteEvent('<block type="dummy_type" x="20" y="20"/>');
fireDeleteEvent(this.workspace, '<block type="dummy_type" x="10" y="10"/>');
fireDeleteEvent(this.workspace, '<block type="dummy_type" x="20" y="20"/>');
chai.assert.equal(this.trashcan.contents_.length, 1);
});
test("Different IDs", function() {
sendDeleteEvent('<block type="dummy_type" id="id1"/>');
sendDeleteEvent('<block type="dummy_type" id="id2"/>');
fireDeleteEvent(this.workspace, '<block type="dummy_type" id="id1"/>');
fireDeleteEvent(this.workspace, '<block type="dummy_type" id="id2"/>');
chai.assert.equal(this.trashcan.contents_.length, 1);
});
test("No Disabled - Disabled True", function() {
sendDeleteEvent('<block type="dummy_type"/>');
sendDeleteEvent('<block type="dummy_type" disabled="true"/>');
fireDeleteEvent(this.workspace, '<block type="dummy_type"/>');
fireDeleteEvent(this.workspace, '<block type="dummy_type" disabled="true"/>');
// 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('<block type="dummy_type"/>');
sendDeleteEvent('<block type="dummy_type" editable="false"/>');
fireDeleteEvent(this.workspace, '<block type="dummy_type"/>');
fireDeleteEvent(this.workspace, '<block type="dummy_type" editable="false"/>');
chai.assert.equal(this.trashcan.contents_.length, 2);
});
test("No Movable - Movable False", function() {
sendDeleteEvent('<block type="dummy_type"/>');
sendDeleteEvent('<block type="dummy_type" movable="false"/>');
fireDeleteEvent(this.workspace, '<block type="dummy_type"/>');
fireDeleteEvent(this.workspace, '<block type="dummy_type" movable="false"/>');
chai.assert.equal(this.trashcan.contents_.length, 2);
});
test("Different Field Values", function() {
sendDeleteEvent(
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
' <field name="dummy_name">dummy_value1</field>' +
'</block>'
);
sendDeleteEvent(
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
' <field name="dummy_name">dummy_value2</field>' +
'</block>'
@@ -119,8 +125,8 @@ suite("Trashcan", function() {
chai.assert.equal(this.trashcan.contents_.length, 2);
});
test("No Values - Values", function() {
sendDeleteEvent('<block type="dummy_type"/>');
sendDeleteEvent(
fireDeleteEvent(this.workspace, '<block type="dummy_type"/>');
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
' <value name="dummy_input">' +
' <block type="dummy_type"/>' +
@@ -130,14 +136,14 @@ suite("Trashcan", function() {
chai.assert.equal(this.trashcan.contents_.length, 2);
});
test("Different Value Blocks", function() {
sendDeleteEvent(
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
' <value name="dummy_input">' +
' <block type="dummy_type1"/>' +
' </value>' +
'</block>'
);
sendDeleteEvent(
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
' <value name="dummy_input">' +
' <block type="dummy_type2"/>' +
@@ -147,8 +153,8 @@ suite("Trashcan", function() {
chai.assert.equal(this.trashcan.contents_.length, 2);
});
test("No Statements - Statements", function() {
sendDeleteEvent('<block type="dummy_type"/>');
sendDeleteEvent(
fireDeleteEvent(this.workspace, '<block type="dummy_type"/>');
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
' <statement name="dummy_input">' +
' <block type="dummy_type"/>' +
@@ -158,14 +164,14 @@ suite("Trashcan", function() {
chai.assert.equal(this.trashcan.contents_.length, 2);
});
test("Different Statement Blocks", function() {
sendDeleteEvent(
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
' <statement name="dummy_input">' +
' <block type="dummy_type1"/>' +
' </statement>' +
'</block>'
);
sendDeleteEvent(
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
' <statement name="dummy_input">' +
' <block type="dummy_type2"/>' +
@@ -175,8 +181,8 @@ suite("Trashcan", function() {
chai.assert.equal(this.trashcan.contents_.length, 2);
});
test("No Next - Next", function() {
sendDeleteEvent('<block type="dummy_type"/>');
sendDeleteEvent(
fireDeleteEvent(this.workspace, '<block type="dummy_type"/>');
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
' <next>' +
' <block type="dummy_type"/>' +
@@ -186,14 +192,14 @@ suite("Trashcan", function() {
chai.assert.equal(this.trashcan.contents_.length, 2);
});
test("Different Next Blocks", function() {
sendDeleteEvent(
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
' <next>' +
' <block type="dummy_type1"/>' +
' </next>' +
'</block>'
);
sendDeleteEvent(
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
' <next>' +
' <block type="dummy_type2"/>' +
@@ -203,8 +209,8 @@ suite("Trashcan", function() {
chai.assert.equal(this.trashcan.contents_.length, 2);
});
test("No Comment - Comment", function() {
sendDeleteEvent('<block type="dummy_type"/>');
sendDeleteEvent(
fireDeleteEvent(this.workspace, '<block type="dummy_type"/>');
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
' <comment>comment_text</comment>' +
'</block>'
@@ -212,12 +218,12 @@ suite("Trashcan", function() {
chai.assert.equal(this.trashcan.contents_.length, 2);
});
test("Different Comment Text", function() {
sendDeleteEvent(
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
' <comment>comment_text1</comment>' +
'</block>'
);
sendDeleteEvent(
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
' <comment>comment_text2</comment>' +
'</block>'
@@ -225,12 +231,12 @@ suite("Trashcan", function() {
chai.assert.equal(this.trashcan.contents_.length, 2);
});
test("Different Comment Size", function() {
sendDeleteEvent(
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
' <comment h="10" w="10">comment_text</comment>' +
'</block>'
);
sendDeleteEvent(
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
' <comment h="20" w="20">comment_text</comment>' +
'</block>'
@@ -239,12 +245,12 @@ suite("Trashcan", function() {
chai.assert.equal(this.trashcan.contents_.length, 1);
});
test("Different Comment Pinned", function() {
sendDeleteEvent(
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
' <comment pinned="false">comment_text</comment>' +
'</block>'
);
sendDeleteEvent(
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
' <comment pinned="true">comment_text</comment>' +
'</block>'
@@ -253,8 +259,8 @@ suite("Trashcan", function() {
chai.assert.equal(this.trashcan.contents_.length, 1);
});
test("No Mutator - Mutator", function() {
sendDeleteEvent('<block type="dummy_type"/>');
sendDeleteEvent(
fireDeleteEvent(this.workspace, '<block type="dummy_type"/>');
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
' <mutation dummy_attribute="dummy_value"></mutation>' +
'</block>'
@@ -262,12 +268,12 @@ suite("Trashcan", function() {
chai.assert.equal(this.trashcan.contents_.length, 2);
});
test("Different Mutator", function() {
sendDeleteEvent(
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
' <mutation dummy_attribute="dummy_value1"></mutation>' +
'</block>'
);
sendDeleteEvent(
fireDeleteEvent(this.workspace,
'<block type="dummy_type">' +
' <mutation dummy_attribute="dummy_value2"></mutation>' +
'</block>'
@@ -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,
'<block type="dummy_type"/>'
);
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('<block type="dummy_type1"/>');
sendDeleteEvent('<block type="dummy_type2"/>');
this.workspace.options.maxTrashcanContents = 1;
fireDeleteEvent(this.workspace, '<block type="dummy_type1"/>');
fireDeleteEvent(this.workspace, '<block type="dummy_type2"/>');
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;
});
});
});

View File

@@ -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');
});

View File

@@ -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(
'<xml xmlns="https://developers.google.com/blockly/xml">' +
' <block type="field_variable_test_block" id="block_id">' +