Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: insertion markers and change events to work with JSO hooks #5378

Merged
merged 5 commits into from
Aug 19, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 26 additions & 9 deletions core/events/block_events.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,23 +193,40 @@ Blockly.Events.BlockChange.prototype.run = function(forward) {
block.setInputsInline(!!value);
break;
case 'mutation':
var oldMutation = '';
if (block.mutationToDom) {
var oldMutationDom = block.mutationToDom();
oldMutation = oldMutationDom && Blockly.Xml.domToText(oldMutationDom);
}
if (block.domToMutation) {
var dom = Blockly.Xml.textToDom(/** @type {string} */ (value) || '<mutation/>');
block.domToMutation(dom);
var oldState = this.getExtraBlockState_(
/** @type {!Blockly.BlockSvg} */ (block));
if (block.loadExtraState) {
block.loadExtraState(JSON.parse(/** @type {string} */ (value) || '{}'));
} else if (block.domToMutation) {
block.domToMutation(
Blockly.Xml.textToDom(
/** @type {string} */ (value) || '<mutation/>'));
}
Blockly.Events.fire(new Blockly.Events.BlockChange(
block, 'mutation', null, oldMutation, value));
block, 'mutation', null, oldState, value));
break;
default:
console.warn('Unknown change type: ' + this.element);
}
};

/**
* Returns the extra state of the given block (either as XML or a JSO, depending
* on the block's definition).
* @param {!Blockly.BlockSvg} block The block to get the extra state of.
* @return {string} A strigified version of the extra state of the given block.
BeksOmega marked this conversation as resolved.
Show resolved Hide resolved
*/
Blockly.Events.BlockChange.prototype.getExtraBlockState_ = function(block) {
if (block.saveExtraState) {
var state = block.saveExtraState();
return state ? JSON.stringify(state) : '';
} else if (block.mutationToDom) {
var state = block.mutationToDom();
return state ? Blockly.Xml.domToText(state) : '';
}
return '';
};

/**
* Class for a block creation event.
* @param {!Blockly.Block=} opt_block The created block. Undefined for a blank
Expand Down
2 changes: 1 addition & 1 deletion core/field.js
Original file line number Diff line number Diff line change
Expand Up @@ -856,11 +856,11 @@ Blockly.Field.prototype.setValue = function(newValue) {
return;
}

this.doValueUpdate_(newValue);
if (source && Blockly.Events.isEnabled()) {
Blockly.Events.fire(new (Blockly.Events.get(Blockly.Events.BLOCK_CHANGE))(
source, 'field', this.name || null, oldValue, newValue));
}
this.doValueUpdate_(newValue);
if (this.isDirty_) {
this.forceRerender();
}
Expand Down
7 changes: 6 additions & 1 deletion core/insertion_marker_manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,12 @@ Blockly.InsertionMarkerManager.prototype.createMarkerBlock_ = function(sourceBlo
try {
var result = this.workspace_.newBlock(imType);
result.setInsertionMarker(true);
if (sourceBlock.mutationToDom) {
if (sourceBlock.saveExtraState) {
var state = sourceBlock.saveExtraState();
if (state) {
result.loadExtraState(state);
}
} else if (sourceBlock.mutationToDom) {
var oldMutationDom = sourceBlock.mutationToDom();
if (oldMutationDom) {
result.domToMutation(oldMutationDom);
Expand Down
29 changes: 22 additions & 7 deletions core/mutator.js
Original file line number Diff line number Diff line change
Expand Up @@ -413,9 +413,8 @@ Blockly.Mutator.prototype.workspaceChanged_ = function(e) {
// When the mutator's workspace changes, update the source block.
if (this.rootBlock_.workspace == this.workspace_) {
Blockly.Events.setGroup(true);
var block = this.block_;
var oldMutationDom = block.mutationToDom();
var oldMutation = oldMutationDom && Blockly.Xml.domToText(oldMutationDom);
var block = /** @type {!Blockly.BlockSvg} */ (this.block_);
var oldExtraState = this.getExtraBlockState_(block);

// Switch off rendering while the source block is rebuilt.
var savedRendered = block.rendered;
Expand All @@ -433,11 +432,10 @@ Blockly.Mutator.prototype.workspaceChanged_ = function(e) {
block.render();
}

var newMutationDom = block.mutationToDom();
var newMutation = newMutationDom && Blockly.Xml.domToText(newMutationDom);
if (oldMutation != newMutation) {
var newExtraState = this.getExtraBlockState_(block);
if (oldExtraState != newExtraState) {
Blockly.Events.fire(new (Blockly.Events.get(Blockly.Events.BLOCK_CHANGE))(
block, 'mutation', null, oldMutation, newMutation));
block, 'mutation', null, oldExtraState, newExtraState));
// Ensure that any bump is part of this mutation's event group.
var group = Blockly.Events.getGroup();
setTimeout(function() {
Expand All @@ -456,6 +454,23 @@ Blockly.Mutator.prototype.workspaceChanged_ = function(e) {
}
};

/**
* Returns the extra state of the given block (either as XML or a JSO, depending
* on the block's definition).
* @param {!Blockly.BlockSvg} block The block to get the extra state of.
* @return {string} A strigified version of the extra state of the given block.
*/
Blockly.Mutator.prototype.getExtraBlockState_ = function(block) {
BeksOmega marked this conversation as resolved.
Show resolved Hide resolved
if (block.saveExtraState) {
var state = block.saveExtraState();
return state ? JSON.stringify(state) : '';
} else if (block.mutationToDom) {
var state = block.mutationToDom();
return state ? Blockly.Xml.domToText(state) : '';
}
return '';
};

/**
* Dispose of this mutator.
*/
Expand Down
1 change: 1 addition & 0 deletions tests/mocha/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"defineRowBlock": true,
"defineStackBlock": true,
"defineStatementBlock": true,
"defineMutatorBlocks": true,
"dispatchPointerEvent": true,
"createFireChangeListenerSpy": true,
"createGenUidStubWithReturns": true,
Expand Down
69 changes: 69 additions & 0 deletions tests/mocha/block_change_event_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@

/**
* @license
* Copyright 2021 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

suite('Block Change Event', function() {
setup(function() {
sharedTestSetup.call(this);
this.workspace = new Blockly.Workspace();
});

teardown(function() {
sharedTestTeardown.call(this);
});

suite('Undo and Redo', function() {
suite('Mutation', function() {
setup(function() {
defineMutatorBlocks();
});

teardown(function() {
Blockly.Extensions.unregister('xml_mutator');
Blockly.Extensions.unregister('jso_mutator');
});

suite('XML', function() {
test('Undo', function() {
const block = this.workspace.newBlock('xml_block', 'block_id');
block.domToMutation(
Blockly.Xml.textToDom('<mutation hasInput="true"/>'));
const blockChange = new Blockly.Events.BlockChange(
block, 'mutation', null, '', '<mutation hasInput="true"/>');
blockChange.run(false);
chai.assert.isFalse(block.hasInput);
});

test('Redo', function() {
const block = this.workspace.newBlock('xml_block', 'block_id');
const blockChange = new Blockly.Events.BlockChange(
block, 'mutation', null, '', '<mutation hasInput="true"/>');
blockChange.run(true);
chai.assert.isTrue(block.hasInput);
});
});

suite('JSO', function() {
test('Undo', function() {
const block = this.workspace.newBlock('jso_block', 'block_id');
block.loadExtraState({hasInput: true});
const blockChange = new Blockly.Events.BlockChange(
block, 'mutation', null, '', '{"hasInput":true}');
blockChange.run(false);
chai.assert.isFalse(block.hasInput);
});

test('Redo', function() {
const block = this.workspace.newBlock('jso_block', 'block_id');
const blockChange = new Blockly.Events.BlockChange(
block, 'mutation', null, '', '{"hasInput":true}');
blockChange.run(true);
chai.assert.isTrue(block.hasInput);
});
});
});
});
});
2 changes: 2 additions & 0 deletions tests/mocha/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
<script src="toolbox_helper.js"></script>

<script src="astnode_test.js"></script>
<script src="block_change_event_test.js"></script>
<script src="block_json_test.js"></script>
<script src="block_test.js"></script>
<script src="comment_test.js"></script>
Expand Down Expand Up @@ -90,6 +91,7 @@
<script src="keydown_test.js"></script>
<script src="logic_ternary_test.js"></script>
<script src="metrics_test.js"></script>
<script src="mutator_test.js"></script>
<script src="names_test.js"></script>
<script src="procedures_test_helpers.js"></script>
<script src="procedures_test.js"></script>
Expand Down
66 changes: 66 additions & 0 deletions tests/mocha/mutator_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@

/**
* @license
* Copyright 2021 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

suite('Mutator', function() {
setup(function() {
sharedTestSetup.call(this);
});

suite('Firing change event', function() {
setup(function() {
this.workspace = Blockly.inject('blocklyDiv', {});
defineMutatorBlocks();
});

teardown(function() {
Blockly.Extensions.unregister('xml_mutator');
Blockly.Extensions.unregister('jso_mutator');
sharedTestTeardown.call(this);
});

test('No change', function() {
var block = createRenderedBlock(this.workspace, 'xml_block');
block.mutator.setVisible(true);
var mutatorWorkspace = block.mutator.getWorkspace();
// Trigger mutator change listener.
createRenderedBlock(mutatorWorkspace, 'checkbox_block');
chai.assert.isTrue(
this.eventsFireStub.getCalls().every(
({args}) =>
args[0].type !== Blockly.Events.BLOCK_CHANGE ||
args[0].element !== 'mutation'));
});

test('XML', function() {
var block = createRenderedBlock(this.workspace, 'xml_block');
block.mutator.setVisible(true);
var mutatorWorkspace = block.mutator.getWorkspace();
mutatorWorkspace.getBlockById('check_block')
.setFieldValue('TRUE', 'CHECK');
chai.assert.isTrue(
this.eventsFireStub.getCalls().some(
({args}) =>
args[0].type === Blockly.Events.BLOCK_CHANGE &&
args[0].element === 'mutation' &&
/<mutation.*><\/mutation>/.test(args[0].newValue)));
});

test('JSO', function() {
var block = createRenderedBlock(this.workspace, 'jso_block');
block.mutator.setVisible(true);
var mutatorWorkspace = block.mutator.getWorkspace();
mutatorWorkspace.getBlockById('check_block')
.setFieldValue('TRUE', 'CHECK');
chai.assert.isTrue(
this.eventsFireStub.getCalls().some(
({args}) =>
args[0].type === Blockly.Events.BLOCK_CHANGE &&
args[0].element === 'mutation' &&
args[0].newValue === '{"hasInput":true}'));
});
});
});
94 changes: 94 additions & 0 deletions tests/mocha/test_helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,7 @@ function defineStatementBlock() {
"helpUrl": ""
}]);
}

function defineBasicBlockWithField() {
Blockly.defineBlocksWithJsonArray([{
"type": "test_field_block",
Expand All @@ -510,6 +511,99 @@ function defineBasicBlockWithField() {
}]);
}

function defineMutatorBlocks() {
Blockly.defineBlocksWithJsonArray([
{
'type': 'xml_block',
'mutator': 'xml_mutator'
},
{
'type': 'jso_block',
'mutator': 'jso_mutator'
},
{
'type': 'checkbox_block',
'message0': '%1',
'args0': [
{
'type': 'field_checkbox',
'name': 'CHECK'
}
]
}
]);

const xmlMutator = {
hasInput: false,

mutationToDom: function() {
var mutation = Blockly.utils.xml.createElement('mutation');
mutation.setAttribute('hasInput', this.hasInput);
return mutation;
},

domToMutation: function(mutation) {
this.hasInput = mutation.getAttribute('hasInput') == 'true';
this.updateShape();
},

decompose: function(workspace) {
var topBlock = workspace.newBlock('checkbox_block', 'check_block');
topBlock.initSvg();
topBlock.render();
return topBlock;
},

compose: function(topBlock) {
this.hasInput = topBlock.getFieldValue('CHECK') == 'TRUE';
this.updateShape();
},

updateShape: function() {
if (this.hasInput && !this.getInput('INPUT')) {
this.appendValueInput('INPUT');
} else if (!this.hasInput && this.getInput('INPUT')) {
this.removeInput('INPUT');
}
}
};
Blockly.Extensions.registerMutator('xml_mutator', xmlMutator);

const jsoMutator = {
hasInput: false,

saveExtraState: function() {
return {hasInput: this.hasInput};
},

loadExtraState: function(state) {
this.hasInput = state.hasInput || false;
this.updateShape();
},

decompose: function(workspace) {
var topBlock = workspace.newBlock('checkbox_block', 'check_block');
topBlock.initSvg();
topBlock.render();
return topBlock;
},

compose: function(topBlock) {
this.hasInput = topBlock.getFieldValue('CHECK') == 'TRUE';
this.updateShape();
},

updateShape: function() {
if (this.hasInput && !this.getInput('INPUT')) {
this.appendValueInput('INPUT');
} else if (!this.hasInput && this.getInput('INPUT')) {
this.removeInput('INPUT');
}
}
};
Blockly.Extensions.registerMutator('jso_mutator', jsoMutator);
}

function createTestBlock() {
return {
id: 'test',
Expand Down