fix(events): Simplify filter function, add new enqueueEvent function (#8539)

* docs(events): JSDoc improvments

* fix(filter): Introduce enqueueEvent; don't reorder in filter

  Remove the broken BlockChange event reordering code from filter.
  Instead, do necessary event reordering (correctly, this time)
  in a new enqueueEvent function used by fireInternal.

* chore(events): Deprecate forward parameter of filter

  Since we don't filter in undo any more, and filtering in reverse
  order causes problems, prepare to remove this opportunity for
  error.

* fix(events): Only merge adjacent events

  Simplify filter by having it consider only adjacent events in the
  queue for merging.

  Previously any events in the queue could potentially be merged if
  they were of suitable (typically identical) .type, even if other
  events had occurred between them (with the sole exception of
  BlockMove events, which would only be merged with adjacent
  events).  This could potentially result in replay failures during
  undo/redo/mirroring, though it is unknown whether any such
  problems occurred in practice.

* refactor(events): Tweaks

  - Use for…of loop where appropriate.
  - Make filter reason-merging code more concise.
  - Use arrow functions when calling Array.prototype.filter.

* chore(events): Fix typos for PR #8539.
This commit is contained in:
Christopher Allen
2024-08-29 17:41:54 +01:00
committed by GitHub
parent 07da975b63
commit a7afda8343
2 changed files with 316 additions and 89 deletions

View File

@@ -42,6 +42,26 @@ suite('Events', function () {
'type': 'simple_test_block',
'message0': 'simple test block',
},
{
'type': 'inputs_test_block',
'message0': 'first %1 second %2',
'args0': [
{
'type': 'input_statement',
'name': 'STATEMENT1',
},
{
'type': 'input_statement',
'name': 'STATEMENT2',
},
],
},
{
'type': 'statement_test_block',
'message0': '',
'previousStatement': null,
'nextStatement': null,
},
]);
});
@@ -1102,6 +1122,165 @@ suite('Events', function () {
});
});
suite('enqueueEvent', function () {
const {FIRE_QUEUE, enqueueEvent} = eventUtils.TEST_ONLY;
function newDisconnectEvent(parent, child, inputName, workspaceId) {
const event = new Blockly.Events.BlockMove(child);
event.workspaceId = workspaceId;
event.oldParentId = parent.id;
event.oldInputName = inputName;
event.oldCoordinate = undefined;
event.newParentId = undefined;
event.newInputName = undefined;
event.newCoordinate = new Blockly.utils.Coordinate(0, 0);
return event;
}
function newConnectEvent(parent, child, inputName, workspaceId) {
const event = new Blockly.Events.BlockMove(child);
event.workspaceId = workspaceId;
event.oldParentId = undefined;
event.oldInputName = undefined;
event.oldCoordinate = new Blockly.utils.Coordinate(0, 0);
event.newParentId = parent.id;
event.newInputName = inputName;
event.newCoordinate = undefined;
return event;
}
function newMutationEvent(block, workspaceId) {
const event = new Blockly.Events.BlockChange(block);
event.workspaceId = workspaceId;
event.element = 'mutation';
return event;
}
test('Events are enqueued', function () {
// Disable events during block creation to avoid firing BlockCreate
// events.
eventUtils.disable();
const block = this.workspace.newBlock('simple_test_block', '1');
eventUtils.enable();
try {
assert.equal(FIRE_QUEUE.length, 0);
const events = [
new Blockly.Events.BlockCreate(block),
new Blockly.Events.BlockMove(block),
new Blockly.Events.Click(block),
];
events.map((e) => enqueueEvent(e));
assert.equal(FIRE_QUEUE.length, events.length, 'FIRE_QUEUE.length');
for (let i = 0; i < events.length; i++) {
assert.equal(FIRE_QUEUE[i], events[i], `FIRE_QUEUE[${i}]`);
}
} finally {
FIRE_QUEUE.length = 0;
}
});
test('BlockChange event reordered', function () {
eventUtils.disable();
const parent = this.workspace.newBlock('inputs_test_block', 'parent');
const child1 = this.workspace.newBlock('statement_test_block', 'child1');
const child2 = this.workspace.newBlock('statement_test_block', 'child2');
eventUtils.enable();
try {
assert.equal(FIRE_QUEUE.length, 0);
const events = [
newDisconnectEvent(parent, child1, 'STATEMENT1'),
newDisconnectEvent(parent, child2, 'STATEMENT2'),
newConnectEvent(parent, child1, 'STATEMENT1'),
newConnectEvent(parent, child2, 'STATEMENT2'),
newMutationEvent(parent),
];
events.map((e) => enqueueEvent(e));
assert.equal(FIRE_QUEUE.length, events.length, 'FIRE_QUEUE.length');
assert.equal(FIRE_QUEUE[0], events[0], 'FIRE_QUEUE[0]');
assert.equal(FIRE_QUEUE[1], events[1], 'FIRE_QUEUE[1]');
assert.equal(FIRE_QUEUE[2], events[4], 'FIRE_QUEUE[2]');
assert.equal(FIRE_QUEUE[3], events[2], 'FIRE_QUEUE[3]');
assert.equal(FIRE_QUEUE[4], events[3], 'FIRE_QUEUE[4]');
} finally {
FIRE_QUEUE.length = 0;
}
});
test('BlockChange event for other workspace not reordered', function () {
eventUtils.disable();
const parent = this.workspace.newBlock('inputs_test_block', 'parent');
const child = this.workspace.newBlock('statement_test_block', 'child');
eventUtils.enable();
try {
assert.equal(FIRE_QUEUE.length, 0);
const events = [
newDisconnectEvent(parent, child, 'STATEMENT1', 'ws1'),
newConnectEvent(parent, child, 'STATEMENT1', 'ws1'),
newMutationEvent(parent, 'ws2'),
];
events.map((e) => enqueueEvent(e));
assert.equal(FIRE_QUEUE.length, events.length, 'FIRE_QUEUE.length');
for (let i = 0; i < events.length; i++) {
assert.equal(FIRE_QUEUE[i], events[i], `FIRE_QUEUE[${i}]`);
}
} finally {
FIRE_QUEUE.length = 0;
}
});
test('BlockChange event for other group not reordered', function () {
eventUtils.disable();
const parent = this.workspace.newBlock('inputs_test_block', 'parent');
const child = this.workspace.newBlock('statement_test_block', 'child');
eventUtils.enable();
try {
assert.equal(FIRE_QUEUE.length, 0);
const events = [];
eventUtils.setGroup('group1');
events.push(newDisconnectEvent(parent, child, 'STATEMENT1'));
events.push(newConnectEvent(parent, child, 'STATEMENT1'));
eventUtils.setGroup('group2');
events.push(newMutationEvent(parent, 'ws2'));
events.map((e) => enqueueEvent(e));
assert.equal(FIRE_QUEUE.length, events.length, 'FIRE_QUEUE.length');
for (let i = 0; i < events.length; i++) {
assert.equal(FIRE_QUEUE[i], events[i], `FIRE_QUEUE[${i}]`);
}
} finally {
FIRE_QUEUE.length = 0;
eventUtils.setGroup(false);
}
});
test('BlockChange event for other parent not reordered', function () {
eventUtils.disable();
const parent1 = this.workspace.newBlock('inputs_test_block', 'parent1');
const parent2 = this.workspace.newBlock('inputs_test_block', 'parent2');
const child = this.workspace.newBlock('statement_test_block', 'child');
eventUtils.enable();
try {
assert.equal(FIRE_QUEUE.length, 0);
const events = [
newDisconnectEvent(parent1, child, 'STATEMENT1', 'ws1'),
newConnectEvent(parent1, child, 'STATEMENT1', 'ws1'),
newMutationEvent(parent2, 'ws2'),
];
events.map((e) => enqueueEvent(e));
assert.equal(FIRE_QUEUE.length, events.length, 'FIRE_QUEUE.length');
for (let i = 0; i < events.length; i++) {
assert.equal(FIRE_QUEUE[i], events[i], `FIRE_QUEUE[${i}]`);
}
} finally {
FIRE_QUEUE.length = 0;
}
});
});
suite('Filters', function () {
function addMoveEvent(events, block, newX, newY) {
events.push(new Blockly.Events.BlockMove(block));