Files
blockly/tests/mocha/toolbox_test.js
Christopher Allen 6f20ac290d refactor(tests): Use import instead of goog.bootstrap to load Blockly in mocha tests (#7406)
* fix(build): Have buildShims clean up up after itself

  We need to create a build/package.json file to allow node.js to
  load build/src/core/blockly.js and the other chunk entry points
  as ES modules (it forcibly assumes .js means CJS even if one is
  trying to import, unless package.json says {"type": "module"}),
  but this interferes with scripts/migration/js2ts doing a
  require('build/deps.js'), which is _not_ an ES module.

  Specific error message was:

  /Users/cpcallen/src/blockly/scripts/migration/js2ts:56
  require(path.resolve(__dirname, '../../build/deps.js'));
  ^

  Error [ERR_REQUIRE_ESM]: require() of ES Module
  /Users/cpcallen/src/blockly/build/deps.js from /Users/cpcallen/src/blockly/scripts/migration/js2ts
  not supported.
  deps.js is treated as an ES module file as it is a .js file whose
  nearest parent package.json contains "type": "module" which
  declares all .js files in that package scope as ES modules.
  Instead rename deps.js to end in .cjs, change the requiring code
  to use dynamic import() which is available in all CommonJS
  modules, or change "type": "module" to "type": "commonjs" in
  /Users/cpcallen/src/blockly/build/package.json to treat all .js
  files as CommonJS (using .mjs for all ES modules instead).

      at Object.<anonymous> (/Users/cpcallen/src/blockly/scripts/migration/js2ts:56:1) {
    code: 'ERR_REQUIRE_ESM'
  }

* chore(tests): Reorder to put interesting script nearer top of file

* chore(tests): Add missing imports of closure/goog/goog.js

  These modules were depending on being loaded via the
  debug module loader, which cannot be used without first loading
  base.js as a script, and thereby defining goog.declareModuleId
  as a side effect—but if they are to be loaded via direct import
  statements then they need to actually import their own
  dependencies.

  This is a temporary measure as soon the goog.declareMouleId
  calls can themselves be deleted.

* refactor(tests): Use import instead of bootstrap to load Blockly

* chores(build): Stop generating deps.mocha.js

  This file was only needed by tests/mocha/index.html's use of
  the debug module loader (via bootstrap.js), which has now been
  removed.

* chore(tests): Remove unneeded goog.declareModuleId calls

  These were only needed because these modules were previously
  being loaded by goog.require and/or goog.bootstrap.

* chores(tests): Remove dead code

  We are fully committed to proper modules now.
2023-08-18 18:06:52 +01:00

773 lines
28 KiB
JavaScript

/**
* @license
* Copyright 2020 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import {defineStackBlock} from './test_helpers/block_definitions.js';
import {
sharedTestSetup,
sharedTestTeardown,
} from './test_helpers/setup_teardown.js';
import {
getBasicToolbox,
getCategoryJSON,
getChildItem,
getCollapsibleItem,
getDeeplyNestedJSON,
getInjectedToolbox,
getNonCollapsibleItem,
getSeparator,
getSimpleJson,
getXmlArray,
} from './test_helpers/toolbox_definitions.js';
suite('Toolbox', function () {
setup(function () {
sharedTestSetup.call(this);
defineStackBlock();
});
teardown(function () {
sharedTestTeardown.call(this);
});
suite('init', function () {
setup(function () {
this.toolbox = getInjectedToolbox();
});
teardown(function () {
this.toolbox.dispose();
});
test('Init called -> HtmlDiv is created', function () {
chai.assert.isDefined(this.toolbox.HtmlDiv);
});
test('Init called -> HtmlDiv is inserted before parent node', function () {
const toolboxDiv = Blockly.common.getMainWorkspace().getInjectionDiv()
.childNodes[0];
chai.assert.equal(
toolboxDiv.className,
'blocklyToolboxDiv blocklyNonSelectable',
);
});
test('Init called -> Toolbox is subscribed to background and foreground colour', function () {
const themeManager = this.toolbox.workspace_.getThemeManager();
const themeManagerSpy = sinon.spy(themeManager, 'subscribe');
const componentManager = this.toolbox.workspace_.getComponentManager();
sinon.stub(componentManager, 'addComponent');
this.toolbox.init();
sinon.assert.calledWith(
themeManagerSpy,
this.toolbox.HtmlDiv,
'toolboxBackgroundColour',
'background-color',
);
sinon.assert.calledWith(
themeManagerSpy,
this.toolbox.HtmlDiv,
'toolboxForegroundColour',
'color',
);
});
test('Init called -> Render is called', function () {
const renderSpy = sinon.spy(this.toolbox, 'render');
const componentManager = this.toolbox.workspace_.getComponentManager();
sinon.stub(componentManager, 'addComponent');
this.toolbox.init();
sinon.assert.calledOnce(renderSpy);
});
test('Init called -> Flyout is initialized', function () {
const componentManager = this.toolbox.workspace_.getComponentManager();
sinon.stub(componentManager, 'addComponent');
this.toolbox.init();
chai.assert.isDefined(this.toolbox.flyout_);
});
});
suite('render', function () {
setup(function () {
this.toolbox = getInjectedToolbox();
});
teardown(function () {
this.toolbox.dispose();
});
test('Render called with valid toolboxDef -> Contents are created', function () {
const positionStub = sinon.stub(this.toolbox, 'position');
this.toolbox.render({
'contents': [
{'kind': 'category', 'contents': []},
{'kind': 'category', 'contents': []},
],
});
chai.assert.lengthOf(this.toolbox.contents_, 2);
sinon.assert.called(positionStub);
});
// TODO: Uncomment once implemented.
test.skip('Toolbox definition with both blocks and categories -> Should throw an error', function () {
const toolbox = this.toolbox;
const badToolboxDef = [
{
'kind': 'block',
},
{
'kind': 'category',
},
];
chai.assert.throws(function () {
toolbox.render({'contents': badToolboxDef});
}, 'Toolbox cannot have both blocks and categories in the root level.');
});
// TODO: Uncomment once implemented.
test.skip('Expanded set to true for a non collapsible toolbox item -> Should open flyout', function () {
this.toolbox.render(this.toolboxXml);
const selectedNode = this.toolbox.tree_.children_[0];
chai.assert.isTrue(selectedNode.selected_);
});
test('JSON toolbox definition -> Should create toolbox with contents', function () {
const jsonDef = {
'contents': [
{
'kind': 'category',
'contents': [
{
'kind': 'block',
'blockxml':
'<block xmlns="http://www.w3.org/1999/xhtml" type="basic_block"><field name="TEXT">FirstCategory-FirstBlock</field></block>',
},
{
'kind': 'label',
'text': 'Input/Output:',
'web-class': 'ioLabel',
},
{
'kind': 'button',
'text': 'insert',
'callbackkey': 'insertConnectionStacks',
'web-class': 'ioLabel',
},
{
'kind': 'sep',
'gap': '7',
},
],
},
],
};
this.toolbox.render(jsonDef);
chai.assert.lengthOf(this.toolbox.contents_, 1);
});
test('multiple icon classes can be applied', function () {
const jsonDef = {
'contents': [
{
'kind': 'category',
'cssConfig': {
'icon': 'customIcon customIconEvents',
},
'contents': [
{
'kind': 'block',
'blockxml':
'<block xmlns="http://www.w3.org/1999/xhtml" type="basic_block"><field name="TEXT">FirstCategory-FirstBlock</field></block>',
},
],
},
],
};
chai.assert.doesNotThrow(() => {
this.toolbox.render(jsonDef);
});
chai.assert.lengthOf(this.toolbox.contents_, 1);
});
});
suite('onClick_', function () {
setup(function () {
this.toolbox = getInjectedToolbox();
});
teardown(function () {
this.toolbox.dispose();
});
test('Toolbox clicked -> Should close flyout', function () {
const hideChaffStub = sinon.stub(
Blockly.WorkspaceSvg.prototype,
'hideChaff',
);
const evt = new PointerEvent('pointerdown', {});
this.toolbox.HtmlDiv.dispatchEvent(evt);
sinon.assert.calledOnce(hideChaffStub);
});
test('Category clicked -> Should select category', function () {
const categoryXml = document.getElementsByClassName('blocklyTreeRow')[0];
const evt = {
'target': categoryXml,
};
const item = this.toolbox.contentMap_[categoryXml.getAttribute('id')];
const setSelectedSpy = sinon.spy(this.toolbox, 'setSelectedItem');
const onClickSpy = sinon.spy(item, 'onClick');
this.toolbox.onClick_(evt);
sinon.assert.calledOnce(setSelectedSpy);
sinon.assert.calledOnce(onClickSpy);
});
});
suite('onKeyDown_', function () {
setup(function () {
this.toolbox = getInjectedToolbox();
});
teardown(function () {
this.toolbox.dispose();
});
function createKeyDownMock(key) {
return {
'key': key,
'preventDefault': function () {},
};
}
function testCorrectFunctionCalled(toolbox, key, funcName) {
const event = createKeyDownMock(key);
const preventDefaultEvent = sinon.stub(event, 'preventDefault');
const selectMethodStub = sinon.stub(toolbox, funcName);
selectMethodStub.returns(true);
toolbox.onKeyDown_(event);
sinon.assert.called(selectMethodStub);
sinon.assert.called(preventDefaultEvent);
}
test('Down button is pushed -> Should call selectNext_', function () {
testCorrectFunctionCalled(this.toolbox, 'ArrowDown', 'selectNext_', true);
});
test('Up button is pushed -> Should call selectPrevious_', function () {
testCorrectFunctionCalled(
this.toolbox,
'ArrowUp',
'selectPrevious_',
true,
);
});
test('Left button is pushed -> Should call selectParent_', function () {
testCorrectFunctionCalled(
this.toolbox,
'ArrowLeft',
'selectParent_',
true,
);
});
test('Right button is pushed -> Should call selectChild_', function () {
testCorrectFunctionCalled(
this.toolbox,
'ArrowRight',
'selectChild_',
true,
);
});
test('Enter button is pushed -> Should toggle expanded', function () {
this.toolbox.selectedItem_ = getCollapsibleItem(this.toolbox);
const toggleExpandedStub = sinon.stub(
this.toolbox.selectedItem_,
'toggleExpanded',
);
const event = createKeyDownMock('Enter');
const preventDefaultEvent = sinon.stub(event, 'preventDefault');
this.toolbox.onKeyDown_(event);
sinon.assert.called(toggleExpandedStub);
sinon.assert.called(preventDefaultEvent);
});
test('Enter button is pushed when no item is selected -> Should not call prevent default', function () {
this.toolbox.selectedItem_ = null;
const event = createKeyDownMock('Enter');
const preventDefaultEvent = sinon.stub(event, 'preventDefault');
this.toolbox.onKeyDown_(event);
sinon.assert.notCalled(preventDefaultEvent);
});
});
suite('Select Methods', function () {
setup(function () {
this.toolbox = getInjectedToolbox();
});
teardown(function () {
this.toolbox.dispose();
});
suite('selectChild_', function () {
test('No item is selected -> Should not handle event', function () {
this.toolbox.selectedItem_ = null;
const handled = this.toolbox.selectChild_();
chai.assert.isFalse(handled);
});
test('Selected item is not collapsible -> Should not handle event', function () {
this.toolbox.selectedItem_ = getNonCollapsibleItem(this.toolbox);
const handled = this.toolbox.selectChild_();
chai.assert.isFalse(handled);
});
test('Selected item is collapsible -> Should expand', function () {
const collapsibleItem = getCollapsibleItem(this.toolbox);
this.toolbox.selectedItem_ = collapsibleItem;
const handled = this.toolbox.selectChild_();
chai.assert.isTrue(handled);
chai.assert.isTrue(collapsibleItem.isExpanded());
chai.assert.equal(this.toolbox.selectedItem_, collapsibleItem);
});
test('Selected item is expanded -> Should select child', function () {
const collapsibleItem = getCollapsibleItem(this.toolbox);
collapsibleItem.expanded_ = true;
const selectNextStub = sinon.stub(this.toolbox, 'selectNext_');
this.toolbox.selectedItem_ = collapsibleItem;
const handled = this.toolbox.selectChild_();
chai.assert.isTrue(handled);
sinon.assert.called(selectNextStub);
});
});
suite('selectParent_', function () {
test('No item selected -> Should not handle event', function () {
this.toolbox.selectedItem_ = null;
const handled = this.toolbox.selectParent_();
chai.assert.isFalse(handled);
});
test('Selected item is expanded -> Should collapse', function () {
const collapsibleItem = getCollapsibleItem(this.toolbox);
collapsibleItem.expanded_ = true;
this.toolbox.selectedItem_ = collapsibleItem;
const handled = this.toolbox.selectParent_();
chai.assert.isTrue(handled);
chai.assert.isFalse(collapsibleItem.isExpanded());
chai.assert.equal(this.toolbox.selectedItem_, collapsibleItem);
});
test('Selected item is not expanded -> Should get parent', function () {
const childItem = getChildItem(this.toolbox);
this.toolbox.selectedItem_ = childItem;
const handled = this.toolbox.selectParent_();
chai.assert.isTrue(handled);
chai.assert.equal(this.toolbox.selectedItem_, childItem.getParent());
});
});
suite('selectNext_', function () {
test('No item is selected -> Should not handle event', function () {
this.toolbox.selectedItem_ = null;
const handled = this.toolbox.selectNext_();
chai.assert.isFalse(handled);
});
test('Next item is selectable -> Should select next item', function () {
const item = this.toolbox.contents_[0];
this.toolbox.selectedItem_ = item;
const handled = this.toolbox.selectNext_();
chai.assert.isTrue(handled);
chai.assert.equal(
this.toolbox.selectedItem_,
this.toolbox.contents_[1],
);
});
test('Selected item is last item -> Should not handle event', function () {
const item = this.toolbox.contents_[this.toolbox.contents_.length - 1];
this.toolbox.selectedItem_ = item;
const handled = this.toolbox.selectNext_();
chai.assert.isFalse(handled);
chai.assert.equal(this.toolbox.selectedItem_, item);
});
test('Selected item is collapsed -> Should skip over its children', function () {
const item = getCollapsibleItem(this.toolbox);
const childItem = item.flyoutItems_[0];
item.expanded_ = false;
this.toolbox.selectedItem_ = item;
const handled = this.toolbox.selectNext_();
chai.assert.isTrue(handled);
chai.assert.notEqual(this.toolbox.selectedItem_, childItem);
});
});
suite('selectPrevious', function () {
test('No item is selected -> Should not handle event', function () {
this.toolbox.selectedItem_ = null;
const handled = this.toolbox.selectPrevious_();
chai.assert.isFalse(handled);
});
test('Selected item is first item -> Should not handle event', function () {
const item = this.toolbox.contents_[0];
this.toolbox.selectedItem_ = item;
const handled = this.toolbox.selectPrevious_();
chai.assert.isFalse(handled);
chai.assert.equal(this.toolbox.selectedItem_, item);
});
test('Previous item is selectable -> Should select previous item', function () {
const item = this.toolbox.contents_[1];
const prevItem = this.toolbox.contents_[0];
this.toolbox.selectedItem_ = item;
const handled = this.toolbox.selectPrevious_();
chai.assert.isTrue(handled);
chai.assert.equal(this.toolbox.selectedItem_, prevItem);
});
test('Previous item is collapsed -> Should skip over children of the previous item', function () {
const childItem = getChildItem(this.toolbox);
const parentItem = childItem.getParent();
const parentIdx = this.toolbox.contents_.indexOf(parentItem);
// Gets the item after the parent.
const item = this.toolbox.contents_[parentIdx + 1];
this.toolbox.selectedItem_ = item;
const handled = this.toolbox.selectPrevious_();
chai.assert.isTrue(handled);
chai.assert.notEqual(this.toolbox.selectedItem_, childItem);
});
});
});
suite('setSelectedItem', function () {
setup(function () {
this.toolbox = getInjectedToolbox();
});
teardown(function () {
this.toolbox.dispose();
});
function setupSetSelected(toolbox, oldItem, newItem) {
toolbox.selectedItem_ = oldItem;
const newItemStub = sinon.stub(newItem, 'setSelected');
toolbox.setSelectedItem(newItem);
return newItemStub;
}
test('Selected item and new item are null -> Should not update the flyout', function () {
this.selectedItem_ = null;
this.toolbox.setSelectedItem(null);
const updateFlyoutStub = sinon.stub(this.toolbox, 'updateFlyout_');
sinon.assert.notCalled(updateFlyoutStub);
});
test('New item is not selectable -> Should not update the flyout', function () {
const separator = getSeparator(this.toolbox);
this.toolbox.setSelectedItem(separator);
const updateFlyoutStub = sinon.stub(this.toolbox, 'updateFlyout_');
sinon.assert.notCalled(updateFlyoutStub);
});
test('Select an item with no children -> Should select item', function () {
const oldItem = getCollapsibleItem(this.toolbox);
const oldItemStub = sinon.stub(oldItem, 'setSelected');
const newItem = getNonCollapsibleItem(this.toolbox);
const newItemStub = setupSetSelected(this.toolbox, oldItem, newItem);
sinon.assert.calledWith(oldItemStub, false);
sinon.assert.calledWith(newItemStub, true);
});
test('Select previously selected item with no children -> Should deselect', function () {
const newItem = getNonCollapsibleItem(this.toolbox);
const newItemStub = setupSetSelected(this.toolbox, newItem, newItem);
sinon.assert.calledWith(newItemStub, false);
});
test('Select collapsible item -> Should select item', function () {
const newItem = getCollapsibleItem(this.toolbox);
const newItemStub = setupSetSelected(this.toolbox, null, newItem);
sinon.assert.calledWith(newItemStub, true);
});
test('Select previously selected collapsible item -> Should not deselect', function () {
const newItem = getCollapsibleItem(this.toolbox);
const newItemStub = setupSetSelected(this.toolbox, newItem, newItem);
sinon.assert.notCalled(newItemStub);
});
});
suite('updateFlyout_', function () {
setup(function () {
this.toolbox = getInjectedToolbox();
});
teardown(function () {
this.toolbox.dispose();
});
function testHideFlyout(toolbox, oldItem, newItem) {
const updateFlyoutStub = sinon.stub(toolbox.flyout_, 'hide');
toolbox.updateFlyout_(oldItem, newItem);
sinon.assert.called(updateFlyoutStub);
}
test('Select previously selected item -> Should close flyout', function () {
const newItem = getNonCollapsibleItem(this.toolbox);
testHideFlyout(this.toolbox, newItem, newItem);
});
test('No new item -> Should close flyout', function () {
testHideFlyout(this.toolbox, null, null);
});
test('Old item but no new item -> Should close flyout', function () {
const oldItem = getNonCollapsibleItem(this.toolbox);
testHideFlyout(this.toolbox, oldItem, null);
});
test('Select collapsible item -> Should close flyout', function () {
const newItem = getCollapsibleItem(this.toolbox);
testHideFlyout(this.toolbox, null, newItem);
});
test('Select selectable item -> Should open flyout', function () {
const showFlyoutstub = sinon.stub(this.toolbox.flyout_, 'show');
const scrollToStartFlyout = sinon.stub(
this.toolbox.flyout_,
'scrollToStart',
);
const newItem = getNonCollapsibleItem(this.toolbox);
this.toolbox.updateFlyout_(null, newItem);
sinon.assert.called(showFlyoutstub);
sinon.assert.called(scrollToStartFlyout);
});
});
suite('position', function () {
setup(function () {
this.toolbox = getBasicToolbox();
const metricsStub = sinon.stub(this.toolbox.workspace_, 'getMetrics');
metricsStub.returns({});
});
function checkHorizontalToolbox(toolbox) {
chai.assert.equal(
toolbox.HtmlDiv.style.left,
'0px',
'Check left position',
);
chai.assert.equal(toolbox.HtmlDiv.style.height, 'auto', 'Check height');
chai.assert.equal(toolbox.HtmlDiv.style.width, '100%', 'Check width');
chai.assert.equal(
toolbox.height_,
toolbox.HtmlDiv.offsetHeight,
'Check height',
);
}
function checkVerticalToolbox(toolbox) {
chai.assert.equal(toolbox.HtmlDiv.style.height, '100%', 'Check height');
chai.assert.equal(
toolbox.width_,
toolbox.HtmlDiv.offsetWidth,
'Check width',
);
}
test('HtmlDiv is not created -> Should not resize', function () {
const toolbox = this.toolbox;
toolbox.HtmlDiv = null;
toolbox.horizontalLayout_ = true;
toolbox.position();
chai.assert.equal(toolbox.height_, 0);
});
test('Horizontal toolbox at top -> Should anchor horizontal toolbox to top', function () {
const toolbox = this.toolbox;
toolbox.toolboxPosition = Blockly.utils.toolbox.Position.TOP;
toolbox.horizontalLayout_ = true;
toolbox.position();
checkHorizontalToolbox(toolbox);
chai.assert.equal(toolbox.HtmlDiv.style.top, '0px', 'Check top');
});
test('Horizontal toolbox at bottom -> Should anchor horizontal toolbox to bottom', function () {
const toolbox = this.toolbox;
toolbox.toolboxPosition = Blockly.utils.toolbox.Position.BOTTOM;
toolbox.horizontalLayout_ = true;
toolbox.position();
checkHorizontalToolbox(toolbox);
chai.assert.equal(toolbox.HtmlDiv.style.bottom, '0px', 'Check bottom');
});
test('Vertical toolbox at right -> Should anchor to right', function () {
const toolbox = this.toolbox;
toolbox.toolboxPosition = Blockly.utils.toolbox.Position.RIGHT;
toolbox.horizontalLayout_ = false;
toolbox.position();
chai.assert.equal(toolbox.HtmlDiv.style.right, '0px', 'Check right');
checkVerticalToolbox(toolbox);
});
test('Vertical toolbox at left -> Should anchor to left', function () {
const toolbox = this.toolbox;
toolbox.toolboxPosition = Blockly.utils.toolbox.Position.LEFT;
toolbox.horizontalLayout_ = false;
toolbox.position();
chai.assert.equal(toolbox.HtmlDiv.style.left, '0px', 'Check left');
checkVerticalToolbox(toolbox);
});
});
suite('parseMethods', function () {
setup(function () {
this.categoryToolboxJSON = getCategoryJSON();
this.simpleToolboxJSON = getSimpleJson();
});
function checkValue(actual, expected, value) {
const actualVal = actual[value];
const expectedVal = expected[value];
chai.assert.equal(
actualVal.toUpperCase(),
expectedVal.toUpperCase(),
'Checking value for: ' + value,
);
}
function checkContents(actualContents, expectedContents) {
chai.assert.equal(actualContents.length, expectedContents.length);
for (let i = 0; i < actualContents.length; i++) {
// TODO: Check the values as well as all the keys.
chai.assert.containsAllKeys(
actualContents[i],
Object.keys(expectedContents[i]),
);
}
}
function checkCategory(actual, expected) {
checkValue(actual, expected, 'kind');
checkValue(actual, expected, 'name');
chai.assert.deepEqual(actual['cssconfig'], expected['cssconfig']);
checkContents(actual.contents, expected.contents);
}
function checkCategoryToolbox(actual, expected) {
const actualContents = actual['contents'];
const expectedContents = expected['contents'];
chai.assert.equal(actualContents.length, expectedContents.length);
for (let i = 0; i < expected.length; i++) {
checkCategory(actualContents[i], expected[i]);
}
}
function checkSimpleToolbox(actual, expected) {
checkContents(actual['contents'], expected['contents']);
}
suite('parseToolbox', function () {
test('Category Toolbox: JSON', function () {
const toolboxDef = Blockly.utils.toolbox.convertToolboxDefToJson(
this.categoryToolboxJSON,
);
chai.assert.isNotNull(toolboxDef);
checkCategoryToolbox(toolboxDef, this.categoryToolboxJSON);
});
test('Simple Toolbox: JSON', function () {
const toolboxDef = Blockly.utils.toolbox.convertToolboxDefToJson(
this.simpleToolboxJSON,
);
chai.assert.isNotNull(toolboxDef);
checkSimpleToolbox(toolboxDef, this.simpleToolboxJSON);
});
test('Category Toolbox: xml', function () {
const toolboxXml = document.getElementById('toolbox-categories');
const toolboxDef =
Blockly.utils.toolbox.convertToolboxDefToJson(toolboxXml);
chai.assert.isNotNull(toolboxDef);
checkCategoryToolbox(toolboxDef, this.categoryToolboxJSON);
});
test('Simple Toolbox: xml', function () {
const toolboxXml = document.getElementById('toolbox-simple');
const toolboxDef =
Blockly.utils.toolbox.convertToolboxDefToJson(toolboxXml);
chai.assert.isNotNull(toolboxDef);
checkSimpleToolbox(toolboxDef, this.simpleToolboxJSON);
});
test('Simple Toolbox: string', function () {
let toolbox = '<xml>';
toolbox += ' <block type="controls_if"></block>';
toolbox += ' <block type="controls_whileUntil"></block>';
toolbox += '</xml>';
const toolboxJson = {
'contents': [
{
'kind': 'block',
'type': 'controls_if',
},
{
'kind': 'block',
'type': 'controls_if',
},
],
};
const toolboxDef =
Blockly.utils.toolbox.convertToolboxDefToJson(toolbox);
chai.assert.isNotNull(toolboxDef);
checkSimpleToolbox(toolboxDef, toolboxJson);
});
test('Category Toolbox: string', function () {
let toolbox = '<xml>';
toolbox += ' <category name="a"></category>';
toolbox += ' <category name="b"></category>';
toolbox += '</xml>';
const toolboxJson = {
'contents': [
{
'kind': 'category',
'name': 'a',
},
{
'kind': 'category',
'name': 'b',
},
],
};
const toolboxDef =
Blockly.utils.toolbox.convertToolboxDefToJson(toolbox);
chai.assert.isNotNull(toolboxDef);
checkSimpleToolbox(toolboxDef, toolboxJson);
});
});
suite('parseFlyout', function () {
test('Array of Nodes', function () {
const xmlList = getXmlArray();
const flyoutDef =
Blockly.utils.toolbox.convertFlyoutDefToJsonArray(xmlList);
checkContents(flyoutDef, this.simpleToolboxJSON['contents']);
});
test('NodeList', function () {
const nodeList = document.getElementById('toolbox-simple').childNodes;
const flyoutDef =
Blockly.utils.toolbox.convertFlyoutDefToJsonArray(nodeList);
checkContents(flyoutDef, this.simpleToolboxJSON['contents']);
});
test('List of json', function () {
const jsonList = this.simpleToolboxJSON['contents'];
const flyoutDef =
Blockly.utils.toolbox.convertFlyoutDefToJsonArray(jsonList);
checkContents(flyoutDef, this.simpleToolboxJSON['contents']);
});
test('Json', function () {
const flyoutDef = Blockly.utils.toolbox.convertFlyoutDefToJsonArray(
this.simpleToolboxJSON,
);
checkContents(flyoutDef, this.simpleToolboxJSON['contents']);
});
});
});
suite('Nested Categories', function () {
setup(function () {
this.toolbox = getInjectedToolbox();
});
teardown(function () {
this.toolbox.dispose();
});
test('Child categories visible if all ancestors expanded', function () {
this.toolbox.render(getDeeplyNestedJSON());
const outerCategory = this.toolbox.contents_[0];
const middleCategory = this.toolbox.contents_[1];
const innerCategory = this.toolbox.contents_[2];
outerCategory.toggleExpanded();
middleCategory.toggleExpanded();
innerCategory.show();
chai.assert.isTrue(
innerCategory.isVisible(),
'All ancestors are expanded, so category should be visible',
);
});
test('Child categories not visible if any ancestor not expanded', function () {
this.toolbox.render(getDeeplyNestedJSON());
const middleCategory = this.toolbox.contents_[1];
const innerCategory = this.toolbox.contents_[2];
// Don't expand the outermost category
// Even though the direct parent of inner is expanded, it shouldn't be visible
// because all ancestor categories need to be visible, not just parent
middleCategory.toggleExpanded();
innerCategory.show();
chai.assert.isFalse(
innerCategory.isVisible(),
'Not all ancestors are expanded, so category should not be visible',
);
});
});
});