mirror of
https://github.com/google/blockly.git
synced 2026-01-11 10:57:07 +01:00
Add getTooltip (#4254)
* Add getTooltip * Add tests * Fix typings? * Fix typings? * PR Comments
This commit is contained in:
@@ -24,6 +24,7 @@ goog.require('Blockly.Extensions');
|
||||
goog.require('Blockly.fieldRegistry');
|
||||
goog.require('Blockly.Input');
|
||||
goog.require('Blockly.navigation');
|
||||
goog.require('Blockly.Tooltip');
|
||||
goog.require('Blockly.utils');
|
||||
goog.require('Blockly.utils.deprecation');
|
||||
goog.require('Blockly.utils.Coordinate');
|
||||
@@ -73,7 +74,7 @@ Blockly.Block = function(workspace, prototypeName, opt_id) {
|
||||
* @private
|
||||
*/
|
||||
this.disabled = false;
|
||||
/** @type {string|!Function} */
|
||||
/** @type {!Blockly.Tooltip.TipInfo} */
|
||||
this.tooltip = '';
|
||||
/** @type {boolean} */
|
||||
this.contextMenu = true;
|
||||
@@ -905,14 +906,23 @@ Blockly.Block.prototype.setHelpUrl = function(url) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Change the tooltip text for a block.
|
||||
* @param {string|!Function} newTip Text for tooltip or a parent element to
|
||||
* link to for its tooltip. May be a function that returns a string.
|
||||
* Sets the tooltip for this block.
|
||||
* @param {!Blockly.Tooltip.TipInfo} newTip The text for the tooltip, a function
|
||||
* that returns the text for the tooltip, or a parent object whose tooltip
|
||||
* will be used. To not display a tooltip pass the empty string.
|
||||
*/
|
||||
Blockly.Block.prototype.setTooltip = function(newTip) {
|
||||
this.tooltip = newTip;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the tooltip text for this block.
|
||||
* @returns {!string} The tooltip text for this block.
|
||||
*/
|
||||
Blockly.Block.prototype.getTooltip = function() {
|
||||
return Blockly.Tooltip.getTooltipOfObject(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the colour of a block.
|
||||
* @return {string} #RRGGBB string.
|
||||
|
||||
@@ -17,6 +17,7 @@ goog.provide('Blockly.Field');
|
||||
goog.require('Blockly.Events');
|
||||
goog.require('Blockly.Events.BlockChange');
|
||||
goog.require('Blockly.Gesture');
|
||||
goog.require('Blockly.Tooltip');
|
||||
goog.require('Blockly.utils');
|
||||
goog.require('Blockly.utils.deprecation');
|
||||
goog.require('Blockly.utils.dom');
|
||||
@@ -66,7 +67,7 @@ Blockly.Field = function(value, opt_validator, opt_config) {
|
||||
/**
|
||||
* Used to cache the field's tooltip value if setTooltip is called when the
|
||||
* field is not yet initialized. Is *not* guaranteed to be accurate.
|
||||
* @type {string|Function|!SVGElement}
|
||||
* @type {?Blockly.Tooltip.TipInfo}
|
||||
* @private
|
||||
*/
|
||||
this.tooltip_ = null;
|
||||
@@ -996,23 +997,36 @@ Blockly.Field.prototype.onMouseDown_ = function(e) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Change the tooltip text for this field.
|
||||
* @param {string|Function|!SVGElement} newTip Text for tooltip or a parent
|
||||
* element to link to for its tooltip.
|
||||
* Sets the tooltip for this field.
|
||||
* @param {?Blockly.Tooltip.TipInfo} newTip The
|
||||
* text for the tooltip, a function that returns the text for the tooltip, a
|
||||
* parent object whose tooltip will be used, or null to display the tooltip
|
||||
* of the parent block. To not display a tooltip pass the empty string.
|
||||
*/
|
||||
Blockly.Field.prototype.setTooltip = function(newTip) {
|
||||
if (!newTip && newTip !== '') { // If null or undefined.
|
||||
newTip = this.sourceBlock_;
|
||||
}
|
||||
var clickTarget = this.getClickTarget_();
|
||||
if (!clickTarget) {
|
||||
if (clickTarget) {
|
||||
clickTarget.tooltip = newTip;
|
||||
} else {
|
||||
// Field has not been initialized yet.
|
||||
this.tooltip_ = newTip;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if (!newTip && newTip !== '') { // If null or undefined.
|
||||
clickTarget.tooltip = this.sourceBlock_;
|
||||
} else {
|
||||
clickTarget.tooltip = newTip;
|
||||
/**
|
||||
* Returns the tooltip text for this field.
|
||||
* @returns {string} The tooltip text for this field.
|
||||
*/
|
||||
Blockly.Field.prototype.getTooltip = function() {
|
||||
var clickTarget = this.getClickTarget_();
|
||||
if (clickTarget) {
|
||||
return Blockly.Tooltip.getTooltipOfObject(clickTarget);
|
||||
}
|
||||
// Field has not been initialized yet. Return stashed this.tooltip_ value.
|
||||
return Blockly.Tooltip.getTooltipOfObject({tooltip: this.tooltip_});
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,6 +23,14 @@ goog.provide('Blockly.Tooltip');
|
||||
|
||||
goog.require('Blockly.utils.string');
|
||||
|
||||
/**
|
||||
* A type which can define a tooltip.
|
||||
* Either a string, an object containing a tooltip property, or a function which
|
||||
* returns either a string, or another arbitrarily nested function which
|
||||
* eventually unwinds to a string.
|
||||
* @typedef {string|{tooltip}|function(): (string|!Function)}
|
||||
*/
|
||||
Blockly.Tooltip.TipInfo;
|
||||
|
||||
/**
|
||||
* Is a tooltip currently showing?
|
||||
@@ -111,6 +119,45 @@ Blockly.Tooltip.MARGINS = 5;
|
||||
*/
|
||||
Blockly.Tooltip.DIV = null;
|
||||
|
||||
/**
|
||||
* Returns the tooltip text for the given element.
|
||||
* @param {?Object} object The object to get the the tooltip text of.
|
||||
* @returns {string} The tooltip text of the element.
|
||||
*/
|
||||
Blockly.Tooltip.getTooltipOfObject = function(object) {
|
||||
var obj = Blockly.Tooltip.getTargetObject_(object);
|
||||
if (obj) {
|
||||
var tooltip = obj.tooltip;
|
||||
while (typeof tooltip == 'function') {
|
||||
tooltip = tooltip();
|
||||
}
|
||||
if (typeof tooltip != 'string') {
|
||||
throw Error('Tooltip function must return a string.');
|
||||
}
|
||||
return tooltip;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the target object that the given object is targeting for its
|
||||
* tooltip. Could be the object itself.
|
||||
* @param {?Object} obj The object are trying to find the target tooltip
|
||||
* object of.
|
||||
* @returns {?{tooltip}} The target tooltip object.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Tooltip.getTargetObject_ = function(obj) {
|
||||
while (obj && obj.tooltip) {
|
||||
if ((typeof obj.tooltip == 'string') ||
|
||||
(typeof obj.tooltip == 'function')) {
|
||||
return obj;
|
||||
}
|
||||
obj = obj.tooltip;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the tooltip div and inject it onto the page.
|
||||
*/
|
||||
@@ -167,11 +214,8 @@ Blockly.Tooltip.onMouseOver_ = function(e) {
|
||||
}
|
||||
// If the tooltip is an object, treat it as a pointer to the next object in
|
||||
// the chain to look at. Terminate when a string or function is found.
|
||||
var element = e.currentTarget;
|
||||
while ((typeof element.tooltip != 'string') &&
|
||||
(typeof element.tooltip != 'function')) {
|
||||
element = element.tooltip;
|
||||
}
|
||||
var element = /** @type {Element} */ (Blockly.Tooltip.getTargetObject_(
|
||||
e.currentTarget));
|
||||
if (Blockly.Tooltip.element_ != element) {
|
||||
Blockly.Tooltip.hide();
|
||||
Blockly.Tooltip.poisonedElement_ = null;
|
||||
@@ -296,11 +340,7 @@ Blockly.Tooltip.show_ = function() {
|
||||
}
|
||||
// Erase all existing text.
|
||||
Blockly.Tooltip.DIV.textContent = '';
|
||||
// Get the new text.
|
||||
var tip = Blockly.Tooltip.element_.tooltip;
|
||||
while (typeof tip == 'function') {
|
||||
tip = tip();
|
||||
}
|
||||
var tip = Blockly.Tooltip.getTooltipOfObject(Blockly.Tooltip.element_);
|
||||
tip = Blockly.utils.string.wrap(tip, Blockly.Tooltip.LIMIT);
|
||||
// Create new text, line by line.
|
||||
var lines = tip.split('\n');
|
||||
|
||||
@@ -83,6 +83,7 @@
|
||||
<script src="registry_test.js"></script>
|
||||
<script src="theme_test.js"></script>
|
||||
<script src="toolbox_test.js"></script>
|
||||
<script src="tooltip_test.js"></script>
|
||||
<script src="trashcan_test.js"></script>
|
||||
<script src="utils_test.js"></script>
|
||||
<script src="variable_map_test.js"></script>
|
||||
|
||||
230
tests/mocha/tooltip_test.js
Normal file
230
tests/mocha/tooltip_test.js
Normal file
@@ -0,0 +1,230 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2020 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
suite('Tooltip', function() {
|
||||
|
||||
setup(function() {
|
||||
sharedTestSetup.call(this);
|
||||
this.workspace = new Blockly.Workspace();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
sharedTestTeardown.call(this);
|
||||
});
|
||||
|
||||
suite('set/getTooltip', function() {
|
||||
setup(function() {
|
||||
Blockly.defineBlocksWithJsonArray([
|
||||
{
|
||||
"type": "test_block",
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_input",
|
||||
"name": "FIELD"
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
delete Blockly.Blocks["test_block"];
|
||||
});
|
||||
|
||||
var tooltipText = 'testTooltip';
|
||||
|
||||
function assertTooltip(obj) {
|
||||
chai.assert.equal(obj.getTooltip(), tooltipText);
|
||||
}
|
||||
|
||||
function setStringTooltip(obj) {
|
||||
obj.setTooltip(tooltipText);
|
||||
}
|
||||
|
||||
function setFunctionTooltip(obj) {
|
||||
obj.setTooltip(() => tooltipText);
|
||||
}
|
||||
|
||||
function setNestedFunctionTooltip(obj) {
|
||||
function nestFunction(fn, count) {
|
||||
if (!count) {
|
||||
return fn;
|
||||
}
|
||||
return () => nestFunction(fn, --count);
|
||||
}
|
||||
obj.setTooltip(nestFunction(() => tooltipText, 5));
|
||||
}
|
||||
|
||||
function setFunctionReturningObjectTooltip(obj) {
|
||||
obj.setTooltip(() => {
|
||||
return {
|
||||
tooltip: tooltipText
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function setObjectTooltip(obj) {
|
||||
obj.setTooltip({tooltip: tooltipText});
|
||||
}
|
||||
|
||||
suite('Headless Blocks', function() {
|
||||
setup(function() {
|
||||
this.block = this.workspace.newBlock('test_block');
|
||||
});
|
||||
|
||||
test('String', function() {
|
||||
setStringTooltip(this.block);
|
||||
assertTooltip(this.block);
|
||||
});
|
||||
|
||||
test('Function', function() {
|
||||
setFunctionTooltip(this.block);
|
||||
assertTooltip(this.block);
|
||||
});
|
||||
|
||||
test('Nested Function', function() {
|
||||
setNestedFunctionTooltip(this.block);
|
||||
assertTooltip(this.block);
|
||||
});
|
||||
|
||||
test('Function returning object', function() {
|
||||
setFunctionReturningObjectTooltip(this.block);
|
||||
chai.assert.throws(this.block.getTooltip.bind(this.block),
|
||||
'Tooltip function must return a string.');
|
||||
});
|
||||
|
||||
test('Object', function() {
|
||||
setObjectTooltip(this.block);
|
||||
assertTooltip(this.block);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Rendered Blocks', function() {
|
||||
setup(function() {
|
||||
this.renderedWorkspace = Blockly.inject('blocklyDiv');
|
||||
this.block = this.renderedWorkspace.newBlock('test_block');
|
||||
this.block.initSvg();
|
||||
this.block.render();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
workspaceTeardown.call(this, this.renderedWorkspace);
|
||||
});
|
||||
|
||||
test('String', function() {
|
||||
setStringTooltip(this.block);
|
||||
assertTooltip(this.block);
|
||||
});
|
||||
|
||||
test('Function', function() {
|
||||
setFunctionTooltip(this.block);
|
||||
assertTooltip(this.block);
|
||||
});
|
||||
|
||||
test('Nested Function', function() {
|
||||
setNestedFunctionTooltip(this.block);
|
||||
assertTooltip(this.block);
|
||||
});
|
||||
|
||||
test('Function returning object', function() {
|
||||
setFunctionReturningObjectTooltip(this.block);
|
||||
chai.assert.throws(this.block.getTooltip.bind(this.block),
|
||||
'Tooltip function must return a string.');
|
||||
});
|
||||
|
||||
test('Object', function() {
|
||||
setObjectTooltip(this.block);
|
||||
assertTooltip(this.block);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Headless Fields', function() {
|
||||
setup(function() {
|
||||
this.block = this.workspace.newBlock('test_block');
|
||||
this.field = this.block.getField('FIELD');
|
||||
});
|
||||
|
||||
test('String', function() {
|
||||
setStringTooltip(this.field);
|
||||
assertTooltip(this.field);
|
||||
});
|
||||
|
||||
test('Function', function() {
|
||||
setFunctionTooltip(this.field);
|
||||
assertTooltip(this.field);
|
||||
});
|
||||
|
||||
test('Nested Function', function() {
|
||||
setNestedFunctionTooltip(this.field);
|
||||
assertTooltip(this.field);
|
||||
});
|
||||
|
||||
test('Function returning object', function() {
|
||||
setFunctionReturningObjectTooltip(this.field);
|
||||
chai.assert.throws(this.field.getTooltip.bind(this.field),
|
||||
'Tooltip function must return a string.');
|
||||
});
|
||||
|
||||
test('Object', function() {
|
||||
setObjectTooltip(this.field);
|
||||
assertTooltip(this.field);
|
||||
});
|
||||
|
||||
test('Null', function() {
|
||||
setStringTooltip(this.block);
|
||||
this.field.setTooltip(null);
|
||||
assertTooltip(this.field);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Rendered Fields', function() {
|
||||
setup(function() {
|
||||
this.renderedWorkspace = Blockly.inject('blocklyDiv');
|
||||
this.block = this.renderedWorkspace.newBlock('test_block');
|
||||
this.block.initSvg();
|
||||
this.block.render();
|
||||
this.field = this.block.getField('FIELD');
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
workspaceTeardown.call(this, this.renderedWorkspace);
|
||||
});
|
||||
|
||||
test('String', function() {
|
||||
setStringTooltip(this.field);
|
||||
assertTooltip(this.field);
|
||||
});
|
||||
|
||||
test('Function', function() {
|
||||
setFunctionTooltip(this.field);
|
||||
assertTooltip(this.field);
|
||||
});
|
||||
|
||||
test('Nested Function', function() {
|
||||
setNestedFunctionTooltip(this.field);
|
||||
assertTooltip(this.field);
|
||||
});
|
||||
|
||||
test('Function returning object', function() {
|
||||
setFunctionReturningObjectTooltip(this.field);
|
||||
chai.assert.throws(this.field.getTooltip.bind(this.field),
|
||||
'Tooltip function must return a string.');
|
||||
});
|
||||
|
||||
test('Object', function() {
|
||||
setObjectTooltip(this.field);
|
||||
assertTooltip(this.field);
|
||||
});
|
||||
|
||||
test('Null', function() {
|
||||
setStringTooltip(this.block);
|
||||
this.field.setTooltip(null);
|
||||
assertTooltip(this.field);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user