mirror of
https://github.com/google/blockly.git
synced 2026-01-07 00:50:27 +01:00
* refactor: Update flyouts to use inflaters. * fix: Specify an axis when creating flyout separators. * chore: Remove unused import. * chore: Fix tests. * chore: Update documentation. * chore: Improve code readability. * refactor: Use null instead of undefined.
639 lines
20 KiB
JavaScript
639 lines
20 KiB
JavaScript
/**
|
|
* @license
|
|
* Copyright 2020 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import {assert} from '../../node_modules/chai/chai.js';
|
|
import {
|
|
sharedTestSetup,
|
|
sharedTestTeardown,
|
|
workspaceTeardown,
|
|
} from './test_helpers/setup_teardown.js';
|
|
import {defineStackBlock} from './test_helpers/block_definitions.js';
|
|
import {
|
|
getBasicToolbox,
|
|
getChildItem,
|
|
getCollapsibleItem,
|
|
getDeeplyNestedJSON,
|
|
getInjectedToolbox,
|
|
getNonCollapsibleItem,
|
|
getProperSimpleJson,
|
|
getSeparator,
|
|
getSimpleJson,
|
|
getXmlArray,
|
|
} from './test_helpers/toolbox_definitions.js';
|
|
|
|
suite('Flyout', function () {
|
|
setup(function () {
|
|
this.clock = sharedTestSetup.call(this, {fireEventsNow: false}).clock;
|
|
Blockly.defineBlocksWithJsonArray([
|
|
{
|
|
'type': 'basic_block',
|
|
'message0': '%1',
|
|
'args0': [
|
|
{
|
|
'type': 'field_input',
|
|
'name': 'TEXT',
|
|
'text': 'default',
|
|
},
|
|
],
|
|
},
|
|
]);
|
|
this.toolboxXml = document.getElementById('toolbox-simple');
|
|
this.workspace = Blockly.inject('blocklyDiv', {
|
|
toolbox: this.toolboxXml,
|
|
});
|
|
});
|
|
|
|
teardown(function () {
|
|
this.clock.runAll();
|
|
sharedTestTeardown.call(this);
|
|
});
|
|
|
|
suite('position', function () {
|
|
suite('vertical flyout', function () {
|
|
suite('simple flyout', function () {
|
|
setup(function () {
|
|
this.flyout = this.workspace.getFlyout();
|
|
this.targetMetricsManager =
|
|
this.flyout.targetWorkspace.getMetricsManager();
|
|
});
|
|
test('y is always 0', function () {
|
|
assert.equal(
|
|
this.flyout.getY(),
|
|
0,
|
|
'y coordinate in vertical flyout should be 0',
|
|
);
|
|
});
|
|
test('x is right of workspace if flyout at right', function () {
|
|
sinon.stub(this.targetMetricsManager, 'getViewMetrics').returns({
|
|
width: 100,
|
|
});
|
|
this.flyout.targetWorkspace.toolboxPosition =
|
|
Blockly.utils.toolbox.Position.RIGHT;
|
|
this.flyout.toolboxPosition_ = Blockly.utils.toolbox.Position.RIGHT;
|
|
assert.equal(
|
|
this.flyout.getX(),
|
|
100,
|
|
'x should be right of workspace',
|
|
);
|
|
});
|
|
test('x is 0 if flyout at left', function () {
|
|
this.flyout.targetWorkspace.toolboxPosition =
|
|
Blockly.utils.toolbox.Position.LEFT;
|
|
this.flyout.toolboxPosition_ = Blockly.utils.toolbox.Position.LEFT;
|
|
assert.equal(
|
|
this.flyout.getX(),
|
|
0,
|
|
'x should be 0 if the flyout is on the left',
|
|
);
|
|
});
|
|
});
|
|
suite('toolbox flyout', function () {
|
|
setup(function () {
|
|
const toolbox = document.getElementById('toolbox-categories');
|
|
this.workspace = Blockly.inject('blocklyDiv', {
|
|
toolbox: toolbox,
|
|
});
|
|
this.flyout = this.workspace.getToolbox().getFlyout();
|
|
this.targetMetricsManager =
|
|
this.flyout.targetWorkspace.getMetricsManager();
|
|
});
|
|
teardown(function () {
|
|
workspaceTeardown.call(this, this.workspace);
|
|
});
|
|
test('x is aligned with toolbox at left', function () {
|
|
sinon.stub(this.targetMetricsManager, 'getToolboxMetrics').returns({
|
|
width: 20,
|
|
});
|
|
this.flyout.setVisible(true);
|
|
this.flyout.targetWorkspace.toolboxPosition =
|
|
Blockly.utils.toolbox.Position.LEFT;
|
|
this.flyout.toolboxPosition_ = Blockly.utils.toolbox.Position.LEFT;
|
|
assert.equal(
|
|
this.flyout.getX(),
|
|
20,
|
|
'x should be aligned with toolbox',
|
|
);
|
|
});
|
|
test('x is aligned with toolbox at right', function () {
|
|
sinon.stub(this.targetMetricsManager, 'getToolboxMetrics').returns({
|
|
width: 20,
|
|
});
|
|
sinon.stub(this.targetMetricsManager, 'getViewMetrics').returns({
|
|
width: 100,
|
|
});
|
|
this.flyout.width_ = 10;
|
|
this.flyout.setVisible(true);
|
|
this.flyout.targetWorkspace.toolboxPosition =
|
|
Blockly.utils.toolbox.Position.RIGHT;
|
|
this.flyout.toolboxPosition_ = Blockly.utils.toolbox.Position.RIGHT;
|
|
assert.equal(
|
|
this.flyout.getX(),
|
|
90,
|
|
'x + width should be aligned with toolbox',
|
|
);
|
|
});
|
|
});
|
|
// These tests simulate a trashcan flyout, i.e. the flyout under test is on the
|
|
// opposite side of the workspace toolbox setting.
|
|
suite('trashcan flyout', function () {
|
|
setup(function () {
|
|
this.flyout = this.workspace.getFlyout();
|
|
this.targetMetricsManager =
|
|
this.flyout.targetWorkspace.getMetricsManager();
|
|
});
|
|
test('x is 0 if trashcan on left', function () {
|
|
sinon.stub(this.flyout.targetWorkspace, 'getMetrics').returns({
|
|
viewWidth: 100,
|
|
});
|
|
this.flyout.targetWorkspace.toolboxPosition =
|
|
Blockly.utils.toolbox.Position.RIGHT;
|
|
this.flyout.toolboxPosition_ = Blockly.utils.toolbox.Position.LEFT;
|
|
assert.equal(
|
|
this.flyout.getX(),
|
|
0,
|
|
'x should be aligned with left edge',
|
|
);
|
|
});
|
|
test('trashcan on right covers right edge of workspace', function () {
|
|
this.flyout.width_ = 20;
|
|
sinon.stub(this.targetMetricsManager, 'getAbsoluteMetrics').returns({
|
|
left: 10,
|
|
});
|
|
sinon.stub(this.targetMetricsManager, 'getViewMetrics').returns({
|
|
width: 100,
|
|
});
|
|
|
|
this.flyout.setVisible(true);
|
|
this.flyout.targetWorkspace.toolboxPosition =
|
|
Blockly.utils.toolbox.Position.LEFT;
|
|
this.flyout.toolboxPosition_ = Blockly.utils.toolbox.Position.RIGHT;
|
|
assert.equal(
|
|
this.flyout.getX(),
|
|
90,
|
|
'x + width should be aligned with right edge',
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
suite('horizontal flyout', function () {
|
|
setup(function () {
|
|
this.workspace = Blockly.inject('blocklyDiv', {
|
|
toolbox: this.toolboxXml,
|
|
horizontalLayout: true,
|
|
});
|
|
});
|
|
teardown(function () {
|
|
workspaceTeardown.call(this, this.workspace);
|
|
});
|
|
suite('simple flyout', function () {
|
|
setup(function () {
|
|
this.flyout = this.workspace.getFlyout();
|
|
this.targetMetricsManager =
|
|
this.flyout.targetWorkspace.getMetricsManager();
|
|
});
|
|
test('x is always 0', function () {
|
|
assert.equal(
|
|
this.flyout.getX(),
|
|
0,
|
|
'x coordinate in horizontal flyout should be 0',
|
|
);
|
|
});
|
|
test('y is 0 if flyout at top', function () {
|
|
this.flyout.targetWorkspace.toolboxPosition =
|
|
Blockly.utils.toolbox.Position.TOP;
|
|
this.flyout.toolboxPosition_ = Blockly.utils.toolbox.Position.TOP;
|
|
assert.equal(
|
|
this.flyout.getY(),
|
|
0,
|
|
'y should be 0 if flyout is at the top',
|
|
);
|
|
});
|
|
test('y is below workspace if flyout at bottom', function () {
|
|
this.flyout.targetWorkspace.toolboxPosition =
|
|
Blockly.utils.toolbox.Position.BOTTOM;
|
|
this.flyout.toolboxPosition_ = Blockly.utils.toolbox.Position.BOTTOM;
|
|
sinon.stub(this.targetMetricsManager, 'getViewMetrics').returns({
|
|
height: 50,
|
|
});
|
|
assert.equal(
|
|
this.flyout.getY(),
|
|
50,
|
|
'y should be below the workspace',
|
|
);
|
|
});
|
|
});
|
|
suite('toolbox flyout', function () {
|
|
setup(function () {
|
|
const toolbox = document.getElementById('toolbox-categories');
|
|
this.workspace = Blockly.inject('blocklyDiv', {
|
|
toolbox: toolbox,
|
|
horizontalLayout: true,
|
|
});
|
|
this.flyout = this.workspace.getToolbox().getFlyout();
|
|
this.targetMetricsManager =
|
|
this.flyout.targetWorkspace.getMetricsManager();
|
|
});
|
|
teardown(function () {
|
|
workspaceTeardown.call(this, this.workspace);
|
|
});
|
|
test('y is aligned with toolbox at top', function () {
|
|
sinon.stub(this.targetMetricsManager, 'getToolboxMetrics').returns({
|
|
height: 20,
|
|
});
|
|
this.flyout.setVisible(true);
|
|
this.flyout.targetWorkspace.toolboxPosition =
|
|
Blockly.utils.toolbox.Position.TOP;
|
|
this.flyout.toolboxPosition_ = Blockly.utils.toolbox.Position.TOP;
|
|
assert.equal(
|
|
this.flyout.getY(),
|
|
20,
|
|
'y should be aligned with toolbox',
|
|
);
|
|
});
|
|
test('y is aligned with toolbox at bottom', function () {
|
|
sinon.stub(this.targetMetricsManager, 'getToolboxMetrics').returns({
|
|
height: 20,
|
|
});
|
|
sinon.stub(this.targetMetricsManager, 'getViewMetrics').returns({
|
|
height: 100,
|
|
});
|
|
this.flyout.height_ = 30;
|
|
this.flyout.setVisible(true);
|
|
this.flyout.targetWorkspace.toolboxPosition =
|
|
Blockly.utils.toolbox.Position.BOTTOM;
|
|
this.flyout.toolboxPosition_ = Blockly.utils.toolbox.Position.BOTTOM;
|
|
assert.equal(
|
|
this.flyout.getY(),
|
|
70,
|
|
'y + height should be aligned with toolbox',
|
|
);
|
|
});
|
|
});
|
|
// These tests simulate a trashcan flyout, i.e. the flyout under test is on the
|
|
// opposite side of the workspace toolbox setting.
|
|
suite('trashcan flyout', function () {
|
|
setup(function () {
|
|
this.flyout = this.workspace.getFlyout();
|
|
this.targetMetricsManager =
|
|
this.flyout.targetWorkspace.getMetricsManager();
|
|
});
|
|
test('y is 0 if trashcan at top', function () {
|
|
this.flyout.targetWorkspace.toolboxPosition =
|
|
Blockly.utils.toolbox.Position.BOTTOM;
|
|
this.flyout.toolboxPosition_ = Blockly.utils.toolbox.Position.TOP;
|
|
assert.equal(this.flyout.getY(), 0, 'y should be aligned with top');
|
|
});
|
|
test('trashcan on bottom covers bottom of workspace', function () {
|
|
this.flyout.targetWorkspace.toolboxPosition =
|
|
Blockly.utils.toolbox.Position.TOP;
|
|
this.flyout.toolboxPosition_ = Blockly.utils.toolbox.Position.BOTTOM;
|
|
sinon.stub(this.targetMetricsManager, 'getAbsoluteMetrics').returns({
|
|
top: 10,
|
|
});
|
|
sinon.stub(this.targetMetricsManager, 'getViewMetrics').returns({
|
|
height: 50,
|
|
});
|
|
this.flyout.setVisible(true);
|
|
this.flyout.height_ = 20;
|
|
assert.equal(
|
|
this.flyout.getY(),
|
|
40,
|
|
'y + height should be aligned with bottom',
|
|
);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
suite('createFlyoutInfo', function () {
|
|
setup(function () {
|
|
this.flyout = this.workspace.getFlyout();
|
|
this.createFlyoutSpy = sinon.spy(this.flyout, 'createFlyoutInfo');
|
|
});
|
|
|
|
function checkFlyoutInfo(flyoutSpy) {
|
|
const flyoutInfo = flyoutSpy.returnValues[0];
|
|
const contents = flyoutInfo;
|
|
|
|
assert.equal(contents.length, 6, 'Contents');
|
|
|
|
assert.equal(contents[0].type, 'block', 'Contents');
|
|
const block = contents[0]['element'];
|
|
assert.instanceOf(block, Blockly.BlockSvg);
|
|
assert.equal(block.getFieldValue('OP'), 'NEQ');
|
|
const childA = block.getInputTargetBlock('A');
|
|
const childB = block.getInputTargetBlock('B');
|
|
assert.isTrue(childA.isShadow());
|
|
assert.isFalse(childB.isShadow());
|
|
assert.equal(childA.getFieldValue('NUM'), 1);
|
|
assert.equal(childB.getFieldValue('NUM'), 2);
|
|
|
|
assert.equal(contents[1].type, 'sep');
|
|
assert.equal(contents[1].element.getBoundingRectangle().getHeight(), 20);
|
|
|
|
assert.equal(contents[2].type, 'button', 'Contents');
|
|
assert.instanceOf(contents[2]['element'], Blockly.FlyoutButton);
|
|
|
|
assert.equal(contents[3].type, 'sep');
|
|
assert.equal(contents[3].element.getBoundingRectangle().getHeight(), 24);
|
|
|
|
assert.equal(contents[4].type, 'label', 'Contents');
|
|
assert.instanceOf(contents[4]['element'], Blockly.FlyoutButton);
|
|
|
|
assert.equal(contents[5].type, 'sep');
|
|
assert.equal(contents[5].element.getBoundingRectangle().getHeight(), 24);
|
|
}
|
|
|
|
suite('Direct show', function () {
|
|
test('Node', function () {
|
|
this.flyout.show(this.toolboxXml);
|
|
checkFlyoutInfo(this.createFlyoutSpy);
|
|
});
|
|
|
|
test('NodeList', function () {
|
|
const nodeList = document.getElementById('toolbox-simple').childNodes;
|
|
this.flyout.show(nodeList);
|
|
checkFlyoutInfo(this.createFlyoutSpy);
|
|
});
|
|
|
|
test('Array of JSON', function () {
|
|
this.flyout.show(getSimpleJson());
|
|
checkFlyoutInfo(this.createFlyoutSpy);
|
|
});
|
|
|
|
test('Array of Proper JSON', function () {
|
|
this.flyout.show(getProperSimpleJson());
|
|
checkFlyoutInfo(this.createFlyoutSpy);
|
|
});
|
|
|
|
test('Array of XML', function () {
|
|
this.flyout.show(getXmlArray());
|
|
checkFlyoutInfo(this.createFlyoutSpy);
|
|
});
|
|
});
|
|
|
|
suite('Dynamic category', function () {
|
|
setup(function () {
|
|
this.stubAndAssert = function (val) {
|
|
sinon
|
|
.stub(
|
|
this.flyout.workspace_.targetWorkspace,
|
|
'getToolboxCategoryCallback',
|
|
)
|
|
.returns(function () {
|
|
return val;
|
|
});
|
|
this.flyout.show('someString');
|
|
checkFlyoutInfo(this.createFlyoutSpy);
|
|
};
|
|
});
|
|
|
|
test('No category available', function () {
|
|
assert.throws(
|
|
function () {
|
|
this.flyout.show('someString');
|
|
}.bind(this),
|
|
"Couldn't find a callback function when opening " +
|
|
'a toolbox category.',
|
|
);
|
|
});
|
|
|
|
test('Node', function () {
|
|
this.stubAndAssert(this.toolboxXml);
|
|
});
|
|
|
|
test('NodeList', function () {
|
|
this.stubAndAssert(
|
|
document.getElementById('toolbox-simple').childNodes,
|
|
);
|
|
});
|
|
|
|
test('Array of JSON', function () {
|
|
this.stubAndAssert(getSimpleJson());
|
|
});
|
|
|
|
test('Array of Proper JSON', function () {
|
|
this.stubAndAssert(getProperSimpleJson());
|
|
});
|
|
|
|
test('Array of XML', function () {
|
|
this.stubAndAssert(getXmlArray());
|
|
});
|
|
});
|
|
});
|
|
|
|
suite('Creating blocks', function () {
|
|
suite('Enabled/Disabled', function () {
|
|
setup(function () {
|
|
this.flyout = this.workspace.getFlyout();
|
|
|
|
this.assertDisabled = function (disabled) {
|
|
const block = this.flyout.getWorkspace().getTopBlocks(false)[0];
|
|
assert.equal(!block.isEnabled(), disabled);
|
|
};
|
|
});
|
|
|
|
suite('XML', function () {
|
|
test('True string', function () {
|
|
const xml = Blockly.utils.xml.textToDom(
|
|
'<xml>' +
|
|
'<block type="text_print" disabled="true"></block>' +
|
|
'</xml>',
|
|
);
|
|
this.flyout.show(xml);
|
|
this.assertDisabled(true);
|
|
});
|
|
|
|
test('False string', function () {
|
|
const xml = Blockly.utils.xml.textToDom(
|
|
'<xml>' +
|
|
'<block type="text_print" disabled="false"></block>' +
|
|
'</xml>',
|
|
);
|
|
this.flyout.show(xml);
|
|
this.assertDisabled(false);
|
|
});
|
|
|
|
test('Disabled string', function () {
|
|
// The XML system supports this for some reason!?
|
|
const xml = Blockly.utils.xml.textToDom(
|
|
'<xml>' +
|
|
'<block type="text_print" disabled="disabled"></block>' +
|
|
'</xml>',
|
|
);
|
|
this.flyout.show(xml);
|
|
this.assertDisabled(true);
|
|
});
|
|
|
|
test('Different string', function () {
|
|
const xml = Blockly.utils.xml.textToDom(
|
|
'<xml>' +
|
|
'<block type="text_print" disabled="random"></block>' +
|
|
'</xml>',
|
|
);
|
|
this.flyout.show(xml);
|
|
this.assertDisabled(false);
|
|
});
|
|
});
|
|
|
|
suite('JSON', function () {
|
|
test('All undefined', function () {
|
|
const json = [
|
|
{
|
|
'kind': 'block',
|
|
'type': 'text_print',
|
|
},
|
|
];
|
|
this.flyout.show(json);
|
|
this.assertDisabled(false);
|
|
});
|
|
|
|
test('Enabled true', function () {
|
|
const json = [
|
|
{
|
|
'kind': 'block',
|
|
'type': 'text_print',
|
|
'enabled': true,
|
|
},
|
|
];
|
|
this.flyout.show(json);
|
|
this.assertDisabled(false);
|
|
});
|
|
|
|
test('Enabled false', function () {
|
|
const json = [
|
|
{
|
|
'kind': 'block',
|
|
'type': 'text_print',
|
|
'enabled': false,
|
|
},
|
|
];
|
|
this.flyout.show(json);
|
|
this.assertDisabled(true);
|
|
});
|
|
|
|
test('Disabled true string', function () {
|
|
const json = [
|
|
{
|
|
'kind': 'block',
|
|
'type': 'text_print',
|
|
'disabled': 'true',
|
|
},
|
|
];
|
|
this.flyout.show(json);
|
|
this.assertDisabled(true);
|
|
});
|
|
|
|
test('Disabled false string', function () {
|
|
const json = [
|
|
{
|
|
'kind': 'block',
|
|
'type': 'text_print',
|
|
'disabled': 'false',
|
|
},
|
|
];
|
|
this.flyout.show(json);
|
|
this.assertDisabled(false);
|
|
});
|
|
|
|
test('Disabled string', function () {
|
|
const json = [
|
|
{
|
|
'kind': 'block',
|
|
'type': 'text_print',
|
|
'disabled': 'disabled', // This is not respected by the JSON!
|
|
},
|
|
];
|
|
this.flyout.show(json);
|
|
this.assertDisabled(false);
|
|
});
|
|
|
|
test('Disabled true value', function () {
|
|
const json = [
|
|
{
|
|
'kind': 'block',
|
|
'type': 'text_print',
|
|
'disabled': true,
|
|
},
|
|
];
|
|
this.flyout.show(json);
|
|
this.assertDisabled(true);
|
|
});
|
|
|
|
test('Disabled false value', function () {
|
|
const json = [
|
|
{
|
|
'kind': 'block',
|
|
'type': 'text_print',
|
|
'disabled': false,
|
|
},
|
|
];
|
|
this.flyout.show(json);
|
|
this.assertDisabled(false);
|
|
});
|
|
|
|
test('Disabled different string', function () {
|
|
const json = [
|
|
{
|
|
'kind': 'block',
|
|
'type': 'text_print',
|
|
'disabled': 'random',
|
|
},
|
|
];
|
|
this.flyout.show(json);
|
|
this.assertDisabled(false);
|
|
});
|
|
|
|
test('Disabled empty string', function () {
|
|
const json = [
|
|
{
|
|
'kind': 'block',
|
|
'type': 'text_print',
|
|
'disabled': '',
|
|
},
|
|
];
|
|
this.flyout.show(json);
|
|
this.assertDisabled(false);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
suite('Recycling', function () {
|
|
setup(function () {
|
|
this.flyout = this.workspace.getFlyout();
|
|
});
|
|
|
|
test('Recycling disabled', function () {
|
|
this.flyout.show({
|
|
'contents': [
|
|
{
|
|
'kind': 'BLOCK',
|
|
'type': 'math_number',
|
|
'fields': {
|
|
'NUM': 123,
|
|
},
|
|
},
|
|
],
|
|
});
|
|
this.flyout.show({
|
|
'contents': [
|
|
{
|
|
'kind': 'BLOCK',
|
|
'type': 'math_number',
|
|
'fields': {
|
|
'NUM': 321,
|
|
},
|
|
},
|
|
],
|
|
});
|
|
const block = this.flyout.workspace_.getAllBlocks()[0];
|
|
assert.equal(block.getFieldValue('NUM'), 321);
|
|
});
|
|
});
|
|
});
|