Merge pull request #1495 from rachel-fenichel/feature/variables_by_id

Act on variables by ID, not name + type
This commit is contained in:
Rachel Fenichel
2017-12-11 15:47:37 -08:00
committed by GitHub
14 changed files with 804 additions and 803 deletions

View File

@@ -266,8 +266,12 @@ Blockly.Blocks['procedures_defnoreturn'] = {
*/
getVarModels: function() {
var vars = [];
// Not fully initialized.
if (!this.arguments_) {
return vars;
}
for (var i = 0, argName; argName = this.arguments_[i]; i++) {
// TODO (#1199): When we switch to tracking variables by ID,
// TODO (#1494): When we switch to tracking procedure arguments by ID,
// update this.
var model = this.workspace.getVariable(argName, '');
if (model) {
@@ -463,12 +467,13 @@ Blockly.Blocks['procedures_mutatorarg'] = {
if (source && source.workspace && source.workspace.options &&
source.workspace.options.parentWorkspace) {
var workspace = source.workspace.options.parentWorkspace;
var variable = workspace.getVariable(newText);
var variableType = '';
var variable = workspace.getVariable(newText, variableType);
// If there is a case change, rename the variable.
if (variable && variable.name !== newText) {
workspace.renameVariableById(variable.getId(), newText);
} else {
workspace.createVariable(newText);
workspace.createVariable(newText, variableType);
}
}
}

View File

@@ -695,9 +695,9 @@ Blockly.Block.prototype.getVarModels = function() {
for (var i = 0, input; input = this.inputList[i]; i++) {
for (var j = 0, field; field = input.fieldRow[j]; j++) {
if (field instanceof Blockly.FieldVariable) {
// TODO (#1199): When we switch to tracking variables by ID,
// update this.
var model = this.workspace.getVariable(field.getValue(), '');
var model = this.workspace.getVariableById(field.getValue());
// Check if the variable actually exists (and isn't just a potential
// variable).
if (model) {
vars.push(model);
}
@@ -709,6 +709,7 @@ Blockly.Block.prototype.getVarModels = function() {
/**
* Notification that a variable is renaming.
* TODO (#1498): consider deleting this.
* If the name matches one of this block's variables, rename it.
* @param {string} oldName Previous name of variable.
* @param {string} newName Renamed variable.
@@ -724,6 +725,41 @@ Blockly.Block.prototype.renameVar = function(oldName, newName) {
}
};
/**
* Notification that a variable is renaming but keeping the same ID. If the
* variable is in use on this block, rerender to show the new name.
* @param {!Blockly.VariableModel} variable The variable being renamed.
* @public
*/
Blockly.Block.prototype.updateVarName = function(variable) {
for (var i = 0, input; input = this.inputList[i]; i++) {
for (var j = 0, field; field = input.fieldRow[j]; j++) {
if (field instanceof Blockly.FieldVariable &&
variable.getId() == field.getValue()) {
field.setText(variable.name);
}
}
}
};
/**
* Notification that a variable is renaming.
* If the name matches one of this block's variables, rename it.
* @param {string} oldId ID of variable to rename.
* @param {string} newId ID of new variable. May be the same as oldId, but with
* an updated name.
*/
Blockly.Block.prototype.renameVarById = function(oldId, newId) {
for (var i = 0, input; input = this.inputList[i]; i++) {
for (var j = 0, field; field = input.fieldRow[j]; j++) {
if (field instanceof Blockly.FieldVariable &&
oldId == field.getValue()) {
field.setValue(newId);
}
}
}
};
/**
* Returns the language-neutral value from the field of a block.
* @param {string} name The name of the field.

View File

@@ -47,15 +47,24 @@ goog.require('goog.string');
* @constructor
*/
Blockly.FieldVariable = function(varname, opt_validator, opt_variableTypes) {
Blockly.FieldVariable.superClass_.constructor.call(this,
Blockly.FieldVariable.dropdownCreate, opt_validator);
this.setValue(varname || '');
// The FieldDropdown constructor would call setValue, which might create a
// spurious variable. Just do the relevant parts of the constructor.
this.menuGenerator_ = Blockly.FieldVariable.dropdownCreate;
this.size_ = new goog.math.Size(0, Blockly.BlockSvg.MIN_BLOCK_Y);
this.setValidator(opt_validator);
// TODO (#1499): Add opt_default_type to match default value. If not set, ''.
this.defaultVariableName = (varname || '');
this.defaultType_ = '';
this.variableTypes = opt_variableTypes;
this.value_ = null;
};
goog.inherits(Blockly.FieldVariable, Blockly.FieldDropdown);
/**
* Install this dropdown on a block.
* Initialize everything needed to render this field. This includes making sure
* that the field's value is valid.
* @public
*/
Blockly.FieldVariable.prototype.init = function() {
if (this.fieldGroup_) {
@@ -68,21 +77,30 @@ Blockly.FieldVariable.prototype.init = function() {
this.initModel();
};
/**
* Initialize the model for this field if it has not already been initialized.
* If the value has not been set to a variable by the first render, we make up a
* variable rather than let the value be invalid.
* @package
*/
Blockly.FieldVariable.prototype.initModel = function() {
if (!this.getValue()) {
// Variables without names get uniquely named for this workspace.
var workspace =
this.sourceBlock_.isInFlyout ?
this.sourceBlock_.workspace.targetWorkspace :
this.sourceBlock_.workspace;
this.setValue(Blockly.Variables.generateUniqueName(workspace));
}
// If the selected variable doesn't exist yet, create it.
// For instance, some blocks in the toolbox have variable dropdowns filled
// in by default.
if (!this.sourceBlock_.isInFlyout) {
this.sourceBlock_.workspace.createVariable(this.getValue());
if (this.variable_) {
return; // Initialization already happened.
}
this.workspace_ = this.sourceBlock_.workspace;
var variable = Blockly.Variables.getOrCreateVariable(
this.workspace_, null, this.defaultVariableName, this.defaultType_);
this.setValue(variable.getId());
};
/**
* Dispose of this field.
* @public
*/
Blockly.FieldVariable.dispose = function() {
Blockly.FieldVariable.superClass_.dispose.call(this);
this.workspace_ = null;
this.variableMap_ = null;
};
/**
@@ -96,39 +114,69 @@ Blockly.FieldVariable.prototype.setSourceBlock = function(block) {
};
/**
* Get the variable's name (use a variableDB to convert into a real name).
* Unline a regular dropdown, variables are literal and have no neutral value.
* @return {string} Current text.
* Get the variable's ID.
* @return {string} Current variable's ID.
*/
Blockly.FieldVariable.prototype.getValue = function() {
return this.getText();
return this.variable_ ? this.variable_.getId() : '';
};
/**
* Set the variable name.
* @param {string} value New text.
* Get the text from this field.
* @return {string} Current text.
*/
Blockly.FieldVariable.prototype.setValue = function(value) {
var newValue = value;
var newText = value;
Blockly.FieldVariable.prototype.getText = function() {
return this.variable_ ? this.variable_.name : '';
};
if (this.sourceBlock_) {
var variable = this.sourceBlock_.workspace.getVariableById(value);
if (variable) {
newText = variable.name;
}
// TODO(marisaleung): Remove name lookup after converting all Field Variable
// instances to use ID instead of name.
else if (variable = this.sourceBlock_.workspace.getVariable(value)) {
newValue = variable.getId();
}
if (Blockly.Events.isEnabled()) {
Blockly.Events.fire(new Blockly.Events.BlockChange(
this.sourceBlock_, 'field', this.name, this.value_, newValue));
/**
* Set the variable ID.
* @param {string} id New variable ID, which must reference an existing
* variable.
*/
Blockly.FieldVariable.prototype.setValue = function(id) {
// What do I do when id is null? That happens when undoing a change event
// for the first time the value was set.
var workspace = this.sourceBlock_.workspace;
var variable = Blockly.Variables.getVariable(workspace, id);
if (!variable) {
throw new Error('Variable id doesn\'t point to a real variable! ID was ' +
id);
}
// Type checks!
var type = variable.type;
if (!this.typeIsAllowed_(type)) {
throw new Error('Variable type doesn\'t match this field! Type was ' +
type);
}
if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
var oldValue = this.variable_ ? this.variable_.getId() : null;
Blockly.Events.fire(new Blockly.Events.BlockChange(
this.sourceBlock_, 'field', this.name, oldValue, variable.getId()));
}
this.variable_ = variable;
this.value_ = id;
this.setText(variable.name);
};
/**
* Check whether the given variable type is allowed on this field.
* @param {string} type The type to check.
* @return {boolean} True if the type is in the list of allowed types.
* @private
*/
Blockly.FieldVariable.prototype.typeIsAllowed_ = function(type) {
var typeList = this.getVariableTypes_();
if (!typeList) {
return true; // If it's null, all types are valid.
}
for (var i = 0; i < typeList.length; i++) {
if (type == typeList[i]) {
return true;
}
}
this.value_ = newValue;
this.setText(newText);
return false;
};
/**
@@ -138,6 +186,8 @@ Blockly.FieldVariable.prototype.setValue = function(value) {
* @private
*/
Blockly.FieldVariable.prototype.getVariableTypes_ = function() {
// TODO: Why does this happen every time, instead of once when the workspace
// is set? Do we expect the variable types to change that much?
var variableTypes = this.variableTypes;
if (variableTypes === null) {
// If variableTypes is null, return all variable types.
@@ -163,10 +213,12 @@ Blockly.FieldVariable.prototype.getVariableTypes_ = function() {
* @this {Blockly.FieldVariable}
*/
Blockly.FieldVariable.dropdownCreate = function() {
if (!this.variable_) {
throw new Error('Tried to call dropdownCreate on a variable field with no' +
' variable selected.');
}
var variableModelList = [];
var name = this.getText();
// Don't create a new variable if there is nothing selected.
var createSelectedVariable = name ? true : false;
var workspace = null;
if (this.sourceBlock_) {
workspace = this.sourceBlock_.workspace;
@@ -181,20 +233,9 @@ Blockly.FieldVariable.dropdownCreate = function() {
var variables = workspace.getVariablesOfType(variableType);
variableModelList = variableModelList.concat(variables);
}
for (var i = 0; i < variableModelList.length; i++) {
if (createSelectedVariable &&
goog.string.caseInsensitiveEquals(variableModelList[i].name, name)) {
createSelectedVariable = false;
break;
}
}
}
// Ensure that the currently selected variable is an option.
if (createSelectedVariable && workspace) {
var newVar = workspace.createVariable(name);
variableModelList.push(newVar);
}
variableModelList.sort(Blockly.VariableModel.compareByName);
var options = [];
for (var i = 0; i < variableModelList.length; i++) {
// Set the UUID as the internal representation of the variable.
@@ -205,6 +246,7 @@ Blockly.FieldVariable.dropdownCreate = function() {
options.push([Blockly.Msg.DELETE_VARIABLE.replace('%1', name),
Blockly.DELETE_VARIABLE_ID]);
}
return options;
};
@@ -217,31 +259,24 @@ Blockly.FieldVariable.dropdownCreate = function() {
*/
Blockly.FieldVariable.prototype.onItemSelected = function(menu, menuItem) {
var id = menuItem.getValue();
// TODO(marisaleung): change setValue() to take in an ID as the parameter.
// Then remove itemText.
var itemText;
if (this.sourceBlock_ && this.sourceBlock_.workspace) {
var workspace = this.sourceBlock_.workspace;
var variable = workspace.getVariableById(id);
// If the item selected is a variable, set itemText to the variable name.
if (variable) {
itemText = variable.name;
} else if (id == Blockly.RENAME_VARIABLE_ID) {
if (id == Blockly.RENAME_VARIABLE_ID) {
// Rename variable.
var currentName = this.getText();
variable = workspace.getVariable(currentName);
Blockly.Variables.renameVariable(workspace, variable);
Blockly.Variables.renameVariable(workspace, this.variable_);
return;
} else if (id == Blockly.DELETE_VARIABLE_ID) {
// Delete variable.
workspace.deleteVariable(this.getText());
workspace.deleteVariableById(this.variable_.getId());
return;
}
// Call any validation function, and allow it to override.
itemText = this.callValidator(itemText);
}
if (itemText !== null) {
this.setValue(itemText);
// TODO: Call any validation function, and allow it to override.
// For now it's unclear whether the validator should act on the id.
//var validatedId = this.callValidator(variable.getId());
}
// if (variable.getId() !== null) {
// this.setValue(validatedId);
// }
this.setValue(id);
};

View File

@@ -542,6 +542,9 @@ Blockly.Flyout.prototype.clearOldBlocks_ = function() {
button.dispose();
}
this.buttons_.length = 0;
// Clear potential variables from the previous showing.
this.workspace_.getPotentialVariableMap().clear();
};
/**
@@ -596,33 +599,6 @@ Blockly.Flyout.prototype.onMouseDown_ = function(e) {
}
};
/**
* Helper function to get the list of variables that have been added to the
* workspace after adding a new block, using the given list of variables that
* were in the workspace before the new block was added.
* @param {!Array.<!Blockly.VariableModel>} originalVariables The array of
* variables that existed in the workspace before adding the new block.
* @return {!Array.<!Blockly.VariableModel>} The new array of variables that were
* freshly added to the workspace after creating the new block, or [] if no
* new variables were added to the workspace.
* @private
*/
Blockly.Flyout.prototype.getAddedVariables_ = function(originalVariables) {
var allCurrentVariables = this.targetWorkspace_.getAllVariables();
var addedVariables = [];
if (originalVariables.length != allCurrentVariables.length) {
for (var i = 0; i < allCurrentVariables.length; i++) {
var variable = allCurrentVariables[i];
// For any variable that is present in allCurrentVariables but not
// present in originalVariables, add the variable to addedVariables.
if (!originalVariables.includes(variable)) {
addedVariables.push(variable);
}
}
}
return addedVariables;
};
/**
* Create a copy of this block on the workspace.
* @param {!Blockly.BlockSvg} originalBlock The block to copy from the flyout.
@@ -643,7 +619,8 @@ Blockly.Flyout.prototype.createBlock = function(originalBlock) {
Blockly.Events.enable();
}
var newVariables = this.getAddedVariables_(variablesBeforeCreation);
var newVariables = Blockly.Variables.getAddedVariables(this.targetWorkspace_,
variablesBeforeCreation);
if (Blockly.Events.isEnabled()) {
Blockly.Events.setGroup(true);

View File

@@ -60,47 +60,78 @@ Blockly.VariableMap.prototype.clear = function() {
/**
* Rename the given variable by updating its name in the variable map.
* @param {Blockly.VariableModel} variable Variable to rename.
* @param {!Blockly.VariableModel} variable Variable to rename.
* @param {string} newName New variable name.
* @param {string=} opt_type The type of the variable to create if variable was
* null.
* @package
*/
Blockly.VariableMap.prototype.renameVariable = function(variable, newName,
opt_type) {
var type = variable ? variable.type : (opt_type || '');
var newVariable = this.getVariable(newName, type);
var variableIndex = -1;
var newVariableIndex = -1;
Blockly.VariableMap.prototype.renameVariable = function(variable, newName) {
var type = variable.type;
var conflictVar = this.getVariable(newName, type);
var blocks = this.workspace.getAllBlocks();
Blockly.Events.setGroup(true);
// The IDs may match if the rename is a simple case change (name1 -> Name1).
if (!conflictVar || conflictVar.getId() == variable.getId()) {
this.renameVariableAndUses_(variable, newName, blocks);
} else {
this.renameVariableWithConflict_(variable, newName, conflictVar, blocks);
}
Blockly.Events.setGroup(false);
};
/**
* Update the name of the given variable and refresh all references to it.
* The new name must not conflict with any existing variable names.
* @param {!Blockly.VariableModel} variable Variable to rename.
* @param {string} newName New variable name.
* @param {!Array.<!Blockly.Block>} blocks The list of all blocks in the
* workspace.
* @private
*/
Blockly.VariableMap.prototype.renameVariableAndUses_ = function(variable,
newName, blocks) {
Blockly.Events.fire(new Blockly.Events.VarRename(variable, newName));
variable.name = newName;
for (var i = 0; i < blocks.length; i++) {
blocks[i].updateVarName(variable);
}
};
/**
* Update the name of the given variable to the same name as an existing
* variable. The two variables are coalesced into a single variable with the ID
* of the existing variable that was already using newName.
* Refresh all references to the variable.
* @param {!Blockly.VariableModel} variable Variable to rename.
* @param {string} newName New variable name.
* @param {!Blockly.VariableModel} conflictVar The variable that was already
* using newName.
* @param {!Array.<!Blockly.Block>} blocks The list of all blocks in the
* workspace.
* @private
*/
Blockly.VariableMap.prototype.renameVariableWithConflict_ = function(variable,
newName, conflictVar, blocks) {
var type = variable.type;
var oldCase = conflictVar.name;
if (newName != oldCase) {
// Simple rename to change the case and update references.
this.renameVariableAndUses_(conflictVar, newName, blocks);
}
// These blocks now refer to a different variable.
// These will fire change events.
for (var i = 0; i < blocks.length; i++) {
blocks[i].renameVarById(variable.getId(), conflictVar.getId());
}
// Finally delete the original variable, which is now unreferenced.
Blockly.Events.fire(new Blockly.Events.VarDelete(variable));
// And remove it from the list.
var variableList = this.getVariablesOfType(type);
if (variable) {
variableIndex = variableList.indexOf(variable);
}
if (newVariable) { // see if I can get rid of newVariable dependency
newVariableIndex = variableList.indexOf(newVariable);
}
var variableIndex = variableList.indexOf(variable);
this.variableMap_[type].splice(variableIndex, 1);
if (variableIndex == -1 && newVariableIndex == -1) {
this.createVariable(newName, '');
console.log('Tried to rename an non-existent variable.');
} else if (variableIndex == newVariableIndex ||
variableIndex != -1 && newVariableIndex == -1) {
// Only changing case, or renaming to a completely novel name.
var variableToRename = this.variableMap_[type][variableIndex];
Blockly.Events.fire(new Blockly.Events.VarRename(variableToRename,
newName));
variableToRename.name = newName;
} else if (variableIndex != -1 && newVariableIndex != -1) {
// Renaming one existing variable to another existing variable.
// The case might have changed, so we update the destination ID.
var variableToRename = this.variableMap_[type][newVariableIndex];
Blockly.Events.fire(new Blockly.Events.VarRename(variableToRename,
newName));
var variableToDelete = this.variableMap_[type][variableIndex];
Blockly.Events.fire(new Blockly.Events.VarDelete(variableToDelete));
variableToRename.name = newName;
this.variableMap_[type].splice(variableIndex, 1);
}
};
/**

View File

@@ -64,14 +64,13 @@ Blockly.Variables.allUsedVariables = function(root) {
var variableHash = Object.create(null);
// Iterate through every block and add each variable to the hash.
for (var x = 0; x < blocks.length; x++) {
// TODO (#1199) Switch to IDs.
var blockVariables = blocks[x].getVars();
var blockVariables = blocks[x].getVarModels();
if (blockVariables) {
for (var y = 0; y < blockVariables.length; y++) {
var varName = blockVariables[y];
// Variable name may be null if the block is only half-built.
if (varName) {
variableHash[varName.toLowerCase()] = varName;
var variable = blockVariables[y];
// Variable ID may be null if the block is only half-built.
if (variable.getId()) {
variableHash[variable.name.toLowerCase()] = variable.name;
}
}
}
@@ -269,30 +268,31 @@ Blockly.Variables.createVariable = function(workspace, opt_callback, opt_type) {
* Rename a variable with the given workspace, variableType, and oldName.
* @param {!Blockly.Workspace} workspace The workspace on which to rename the
* variable.
* @param {?Blockly.VariableModel} variable Variable to rename.
* @param {Blockly.VariableModel} variable Variable to rename.
* @param {function(?string=)=} opt_callback A callback. It will
* be passed an acceptable new variable name, or null if change is to be
* aborted (cancel button), or undefined if an existing variable was chosen.
*/
Blockly.Variables.renameVariable = function(workspace, variable,
opt_callback) {
opt_callback) {
// This function needs to be named so it can be called recursively.
var promptAndCheckWithAlert = function(defaultName) {
Blockly.Variables.promptName(
Blockly.Msg.RENAME_VARIABLE_TITLE.replace('%1', variable.name), defaultName,
function(newName) {
if (newName) {
workspace.renameVariable(variable.name, newName);
if (opt_callback) {
opt_callback(newName);
var promptText =
Blockly.Msg.RENAME_VARIABLE_TITLE.replace('%1', variable.name);
Blockly.Variables.promptName(promptText, defaultName,
function(newName) {
if (newName) {
workspace.renameVariableById(variable.getId(), newName);
if (opt_callback) {
opt_callback(newName);
}
} else {
// User canceled prompt without a value.
if (opt_callback) {
opt_callback(null);
}
}
} else {
// User canceled prompt without a value.
if (opt_callback) {
opt_callback(null);
}
}
});
});
};
promptAndCheckWithAlert('');
};
@@ -330,12 +330,122 @@ Blockly.Variables.promptName = function(promptText, defaultText, callback) {
Blockly.Variables.generateVariableFieldXml_ = function(variableModel) {
// The variable name may be user input, so it may contain characters that need
// to be escaped to create valid XML.
var element = goog.dom.createDom('field');
element.setAttribute('name', 'VAR');
element.setAttribute('variabletype', variableModel.type);
element.setAttribute('id', variableModel.getId());
element.textContent = variableModel.name;
var xmlString = Blockly.Xml.domToText(element);
return xmlString;
var typeString = variableModel.type;
if (typeString == '') {
typeString = '\'\'';
}
var text = '<field name="VAR" id="' + variableModel.getId() +
'" variabletype="' + typeString +
'">' + goog.string.htmlEscape(variableModel.name) + '</field>';
return text;
};
/**
* Helper function to look up or create a variable on the given workspace.
* If no variable exists, creates and returns it.
* @param {!Blockly.Workspace} workspace The workspace to search for the
* variable. It may be a flyout workspace or main workspace.
* @param {string} id The ID to use to look up or create the variable, or null.
* @param {string} name The string to use to look up or create the variable,
* @param {string} type The type to use to look up or create the variable.
* @return {!Blockly.VariableModel} The variable corresponding to the given ID
* or name + type combination.
* @package
*/
Blockly.Variables.getOrCreateVariable = function(workspace, id, name, type) {
var variable = Blockly.Variables.getVariable(workspace, id, name, type);
if (!variable) {
variable = Blockly.Variables.createVariable_(workspace, id, name, type);
}
return variable;
};
/**
* Look up a variable on the given workspace.
* Always looks in the main workspace before looking in the flyout workspace.
* Always prefers lookup by ID to lookup by name + type.
* @param {!Blockly.Workspace} workspace The workspace to search for the
* variable. It may be a flyout workspace or main workspace.
* @param {string} id The ID to use to look up the variable, or null.
* @param {string=} opt_name The string to use to look up the variable. Only
* used if lookup by ID fails.
* @param {string=} opt_type The type to use to look up the variable. Only used
* if lookup by ID fails.
* @return {?Blockly.VariableModel} The variable corresponding to the given ID
* or name + type combination, or null if not found.
* @private
*/
Blockly.Variables.getVariable = function(workspace, id, opt_name, opt_type) {
var potentialVariableMap = workspace.getPotentialVariableMap();
// Try to just get the variable, by ID if possible.
if (id) {
// Look in the real variable map before checking the potential variable map.
var variable = workspace.getVariableById(id);
if (!variable && potentialVariableMap) {
variable = potentialVariableMap.getVariableById(id);
}
} else if (opt_name && (opt_type != undefined)){
// Otherwise look up by name and type.
var variable = workspace.getVariable(opt_name, opt_type);
if (!variable && potentialVariableMap) {
variable = potentialVariableMap.getVariable(opt_name, opt_type);
}
}
return variable;
};
/**
* Helper function to create a variable on the given workspace.
* @param {!Blockly.Workspace} workspace The workspace in which to create the
* variable. It may be a flyout workspace or main workspace.
* @param {string} id The ID to use to create the variable, or null.
* @param {string} name The string to use to create the variable.
* @param {string} type The type to use to create the variable.
* @return {!Blockly.VariableModel} The variable corresponding to the given ID
* or name + type combination.
* @private
*/
Blockly.Variables.createVariable_ = function(workspace, id, name, type) {
var potentialVariableMap = workspace.getPotentialVariableMap();
// Variables without names get uniquely named for this workspace.
if (!name) {
var ws = workspace.isFlyout ? workspace.targetWorkspace : workspace;
name = Blockly.Variables.generateUniqueName(ws);
}
// Create a potential variable if in the flyout.
if (potentialVariableMap) {
var variable = potentialVariableMap.createVariable(name, type, id);
} else { // In the main workspace, create a real variable.
var variable = workspace.createVariable(name, type, id);
}
return variable;
};
/**
* Helper function to get the list of variables that have been added to the
* workspace after adding a new block, using the given list of variables that
* were in the workspace before the new block was added.
* @param {!Blockly.Workspace} workspace The workspace to inspect.
* @param {!Array.<!Blockly.VariableModel>} originalVariables The array of
* variables that existed in the workspace before adding the new block.
* @return {!Array.<!Blockly.VariableModel>} The new array of variables that were
* freshly added to the workspace after creating the new block, or [] if no
* new variables were added to the workspace.
* @package
*/
Blockly.Variables.getAddedVariables = function(workspace, originalVariables) {
var allCurrentVariables = workspace.getAllVariables();
var addedVariables = [];
if (originalVariables.length != allCurrentVariables.length) {
for (var i = 0; i < allCurrentVariables.length; i++) {
var variable = allCurrentVariables[i];
// For any variable that is present in allCurrentVariables but not
// present in originalVariables, add the variable to addedVariables.
if (!originalVariables.includes(variable)) {
addedVariables.push(variable);
}
}
}
return addedVariables;
};

View File

@@ -84,6 +84,18 @@ Blockly.Workspace = function(opt_options) {
* @private
*/
this.variableMap_ = new Blockly.VariableMap(this);
/**
* Blocks in the flyout can refer to variables that don't exist in the
* workspace. For instance, the "get item in list" block refers to an "item"
* variable regardless of whether the variable has been created yet.
* A FieldVariable must always refer to a Blockly.VariableModel. We reconcile
* these by tracking "potential" variables in the flyout. These variables
* become real when references to them are dragged into the main workspace.
* @type {!Blockly.VariableMap}
* @private
*/
this.potentialVariableMap_ = new Blockly.VariableMap(this);
};
/**
@@ -197,91 +209,7 @@ Blockly.Workspace.prototype.clear = function() {
Blockly.Events.setGroup(false);
}
this.variableMap_.clear();
};
/**
* Walk the workspace and update the map of variables to only contain ones in
* use on the workspace. Use when loading new workspaces from disk.
* @param {boolean} clear True if the old variable map should be cleared.
*/
Blockly.Workspace.prototype.updateVariableStore = function(clear) {
// TODO: Sort
if (this.isFlyout) {
return;
}
var variableNames = Blockly.Variables.allUsedVariables(this);
var varList = [];
for (var i = 0, name; name = variableNames[i]; i++) {
// Get variable model with the used variable name.
var tempVar = this.getVariable(name);
if (tempVar) {
varList.push({'name': tempVar.name, 'type': tempVar.type,
'id': tempVar.getId()});
} else {
varList.push({'name': name, 'type': null, 'id': null});
// TODO(marisaleung): Use variable.type and variable.getId() once variable
// instances are storing more than just name.
}
}
if (clear) {
this.variableMap_.clear();
}
// Update the list in place so that the flyout's references stay correct.
for (var i = 0, varDict; varDict = varList[i]; i++) {
if (!this.getVariable(varDict.name)) {
this.createVariable(varDict.name, varDict.type, varDict.id);
}
}
};
/**
* Rename a variable by updating its name in the variable map. Identify the
* variable to rename with the given variable.
* @param {?Blockly.VariableModel} variable Variable to rename.
* @param {string} newName New variable name.
* @param {string=} opt_type The type of the variable to create if variable was
* null.
*/
Blockly.Workspace.prototype.renameVariableInternal_ = function(
variable, newName, opt_type) {
var type = variable ? variable.type : (opt_type || '');
var newVariable = this.getVariable(newName, type);
var oldCase;
// Find if newVariable case is different.
if (newVariable && newVariable.name != newName) {
oldCase = newVariable.name;
}
Blockly.Events.setGroup(true);
var blocks = this.getAllBlocks();
// Iterate through every block and update name.
for (var i = 0; i < blocks.length; i++) {
blocks[i].renameVar(variable.name, newName);
if (oldCase) {
blocks[i].renameVar(oldCase, newName);
}
}
this.variableMap_.renameVariable(variable, newName, type);
Blockly.Events.setGroup(false);
};
/**
* Rename a variable by updating its name in the variable map. Identify the
* variable to rename with the given name.
* TODO (#1199): Possibly delete this function.
* @param {string} oldName Variable to rename.
* @param {string} newName New variable name.
* @param {string=} opt_type The type of the variable. If not provided it
* defaults to the empty string, which is a specific type.
*/
Blockly.Workspace.prototype.renameVariable = function(oldName, newName,
opt_type) {
var type = opt_type || '';
// Warning: Prefer to use renameVariableById.
var variable = this.getVariable(oldName, type);
this.renameVariableInternal_(variable, newName, opt_type);
this.potentialVariableMap_.clear();
};
/**
@@ -292,7 +220,11 @@ Blockly.Workspace.prototype.renameVariable = function(oldName, newName,
*/
Blockly.Workspace.prototype.renameVariableById = function(id, newName) {
var variable = this.getVariableById(id);
this.renameVariableInternal_(variable, newName);
if (!variable) {
throw new Error('Tried to rename a variable that didn\'t exist. ID: ' + id);
}
this.variableMap_.renameVariable(variable, newName);
};
/**
@@ -312,14 +244,10 @@ Blockly.Workspace.prototype.createVariable = function(name, opt_type, opt_id) {
/**
* Find all the uses of a named variable.
* TODO (#1199): Possibly delete this function.
* @param {string} name Name of variable.
* @param {string=} opt_type The type of the variable. If not provided it
* defaults to the empty string, which is a specific type.
* @param {string} id ID of the variable to find.
* @return {!Array.<!Blockly.Block>} Array of block usages.
*/
Blockly.Workspace.prototype.getVariableUses = function(name, opt_type) {
var type = opt_type || '';
Blockly.Workspace.prototype.getVariableUsesById = function(id) {
var uses = [];
var blocks = this.getAllBlocks();
// Iterate through every block and check the name.
@@ -327,14 +255,7 @@ Blockly.Workspace.prototype.getVariableUses = function(name, opt_type) {
var blockVariables = blocks[i].getVarModels();
if (blockVariables) {
for (var j = 0; j < blockVariables.length; j++) {
var varModel = blockVariables[j];
var varName = varModel.name;
// Skip variables of the wrong type.
if (varModel.type != type) {
continue;
}
// Variable name may be null if the block is only half-built.
if (varName && name && Blockly.Names.equals(varName, name)) {
if (blockVariables[j].getId() == id) {
uses.push(blocks[i]);
}
}
@@ -343,49 +264,6 @@ Blockly.Workspace.prototype.getVariableUses = function(name, opt_type) {
return uses;
};
/**
* Delete a variable by the passed in name and all of its uses from this
* workspace. May prompt the user for confirmation.
* TODO (#1199): Possibly delete this function.
* @param {string} name Name of variable to delete.
* @param {string=} opt_type The type of the variable. If not provided it
* defaults to the empty string, which is a specific type.
*/
Blockly.Workspace.prototype.deleteVariable = function(name, opt_type) {
var type = opt_type || '';
// Check whether this variable is a function parameter before deleting.
var uses = this.getVariableUses(name, type);
for (var i = 0, block; block = uses[i]; i++) {
if (block.type == 'procedures_defnoreturn' ||
block.type == 'procedures_defreturn') {
var procedureName = block.getFieldValue('NAME');
Blockly.alert(
Blockly.Msg.CANNOT_DELETE_VARIABLE_PROCEDURE.
replace('%1', name).
replace('%2', procedureName));
return;
}
}
var workspace = this;
var variable = workspace.getVariable(name, type);
if (uses.length > 1) {
// Confirm before deleting multiple blocks.
Blockly.confirm(
Blockly.Msg.DELETE_VARIABLE_CONFIRMATION.replace('%1',
String(uses.length)).
replace('%2', name),
function(ok) {
if (ok) {
workspace.deleteVariableInternal_(variable);
}
});
} else {
// No confirmation necessary for a single block.
this.deleteVariableInternal_(variable);
}
};
/**
* Delete a variables by the passed in ID and all of its uses from this
* workspace. May prompt the user for confirmation.
@@ -394,7 +272,37 @@ Blockly.Workspace.prototype.deleteVariable = function(name, opt_type) {
Blockly.Workspace.prototype.deleteVariableById = function(id) {
var variable = this.getVariableById(id);
if (variable) {
this.deleteVariableInternal_(variable);
// Check whether this variable is a function parameter before deleting.
var variableName = variable.name;
var uses = this.getVariableUsesById(id);
for (var i = 0, block; block = uses[i]; i++) {
if (block.type == 'procedures_defnoreturn' ||
block.type == 'procedures_defreturn') {
var procedureName = block.getFieldValue('NAME');
var deleteText = Blockly.Msg.CANNOT_DELETE_VARIABLE_PROCEDURE.
replace('%1', variableName).
replace('%2', procedureName);
Blockly.alert(deleteText);
return;
}
}
var workspace = this;
if (uses.length > 1) {
// Confirm before deleting multiple blocks.
var confirmText = Blockly.Msg.DELETE_VARIABLE_CONFIRMATION.
replace('%1', String(uses.length)).
replace('%2', variableName);
Blockly.confirm(confirmText,
function(ok) {
if (ok) {
workspace.deleteVariableInternal_(variable, uses);
}
});
} else {
// No confirmation necessary for a single block.
this.deleteVariableInternal_(variable, uses);
}
} else {
console.warn("Can't delete non-existent variable: " + id);
}
@@ -404,10 +312,10 @@ Blockly.Workspace.prototype.deleteVariableById = function(id) {
* Deletes a variable and all of its uses from this workspace without asking the
* user for confirmation.
* @param {!Blockly.VariableModel} variable Variable to delete.
* @param {!Array.<!Blockly.Block>} uses An array of uses of the variable.
* @private
*/
Blockly.Workspace.prototype.deleteVariableInternal_ = function(variable) {
var uses = this.getVariableUses(variable.name);
Blockly.Workspace.prototype.deleteVariableInternal_ = function(variable, uses) {
Blockly.Events.setGroup(true);
for (var i = 0; i < uses.length; i++) {
uses[i].dispose(true, false);
@@ -621,6 +529,25 @@ Blockly.Workspace.prototype.getAllVariables = function() {
return this.variableMap_.getAllVariables();
};
/**
* Return the variable map that contains "potential" variables. These exist in
* the flyout but not in the workspace.
* @return {?Blockly.VariableMap} The potential variable map.
* @package
*/
Blockly.Workspace.prototype.getPotentialVariableMap = function() {
return this.isFlyout ? this.potentialVariableMap_ : null;
};
/**
* Return the map of all variables on the workspace.
* @return {?Blockly.VariableMap} The variable map.
* @package
*/
Blockly.Workspace.prototype.getVariableMap = function() {
return this.variableMap_;
};
/**
* Database of all workspaces.
* @private

View File

@@ -86,6 +86,30 @@ Blockly.Xml.blockToDomWithXY = function(block, opt_noId) {
return element;
};
Blockly.Xml.fieldToDomVariable_ = function(field, workspace) {
var id = field.getValue();
var variable = workspace.getVariableById(id);
if (!variable) {
if (workspace.isFlyout && workspace.targetWorkspace) {
var potentialVariableMap = workspace.getPotentialVariableMap();
if (potentialVariableMap) {
variable = potentialVariableMap.getVariableById(id);
}
}
}
if (variable) {
var container = goog.dom.createDom('field', null, variable.name);
container.setAttribute('name', field.name);
container.setAttribute('id', variable.getId());
container.setAttribute('variabletype', variable.type);
return container;
} else {
// something went wrong?
console.warn('no variable in fieldtodom');
return null;
}
};
/**
* Encode a field as XML.
* @param {!Blockly.Field} field The field to encode.
@@ -96,16 +120,13 @@ Blockly.Xml.blockToDomWithXY = function(block, opt_noId) {
*/
Blockly.Xml.fieldToDom_ = function(field, workspace) {
if (field.name && field.EDITABLE) {
var container = goog.dom.createDom('field', null, field.getValue());
container.setAttribute('name', field.name);
if (field instanceof Blockly.FieldVariable) {
var variable = workspace.getVariable(field.getValue());
if (variable) {
container.setAttribute('id', variable.getId());
container.setAttribute('variabletype', variable.type);
}
return Blockly.Xml.fieldToDomVariable_(field, workspace);
} else {
var container = goog.dom.createDom('field', null, field.getValue());
container.setAttribute('name', field.name);
return container;
}
return container;
}
return null;
};
@@ -397,7 +418,6 @@ Blockly.Xml.domToWorkspace = function(xml, workspace) {
}
Blockly.Field.stopCache();
}
workspace.updateVariableStore(false);
// Re-enable workspace resizing.
if (workspace.setResizesEnabled) {
workspace.setResizesEnabled(true);
@@ -476,9 +496,11 @@ Blockly.Xml.domToBlock = function(xmlBlock, workspace) {
}
// Create top-level block.
Blockly.Events.disable();
var variablesBeforeCreation = workspace.getAllVariables();
try {
var topBlock = Blockly.Xml.domToBlockHeadless_(xmlBlock, workspace);
if (workspace.rendered) {
// TODO (fenichel): Otherwise call initModel?
// Hide connections to speed up assembly.
topBlock.setConnectionsHidden(true);
// Generate list of all blocks.
@@ -507,6 +529,13 @@ Blockly.Xml.domToBlock = function(xmlBlock, workspace) {
}
if (Blockly.Events.isEnabled()) {
Blockly.Events.fire(new Blockly.Events.BlockCreate(topBlock));
var newVariables = Blockly.Variables.getAddedVariables(workspace,
variablesBeforeCreation);
// Fire a VarCreate event for each (if any) new variable created.
for(var i = 0; i < newVariables.length; i++) {
var thisVariable = newVariables[i];
Blockly.Events.fire(new Blockly.Events.VarCreate(thisVariable));
}
}
return topBlock;
};
@@ -698,6 +727,37 @@ Blockly.Xml.domToBlockHeadless_ = function(xmlBlock, workspace) {
return block;
};
/**
* Decode an XML variable field tag and set the value of that field.
* @param {!Blockly.Workspace} workspace The workspace that is currently being
* deserialized.
* @param {!Element} xml The field tag to decode.
* @param {string} text The text content of the XML tag.
* @param {!Blockly.FieldVariable} field The field on which the value will be
* set.
* @private
*/
Blockly.Xml.domToFieldVariable_ = function(workspace, xml, text, field) {
var type = xml.getAttribute('variabletype') || '';
// TODO (fenichel): Does this need to be explicit or not?
if (type == '\'\'') {
type = '';
}
var variable =
Blockly.Variables.getOrCreateVariable(workspace, xml.id, text, type);
// This should never happen :)
if (type != null && type !== variable.type) {
throw Error('Serialized variable type with id \'' +
variable.getId() + '\' had type ' + variable.type + ', and ' +
'does not match variable field that references it: ' +
Blockly.Xml.domToText(xml) + '.');
}
field.setValue(variable.getId());
};
/**
* Decode an XML field tag and set the value of that field on the given block.
* @param {!Blockly.Block} block The block that is currently being deserialized.
@@ -716,29 +776,10 @@ Blockly.Xml.domToField_ = function(block, fieldName, xml) {
var workspace = block.workspace;
var text = xml.textContent;
if (field instanceof Blockly.FieldVariable) {
// TODO (#1199): When we change setValue and getValue to
// interact with IDs instead of names, update this so that we get
// the variable based on ID instead of textContent.
var type = xml.getAttribute('variabletype') || '';
// TODO: Consider using a different name (varID?) because this is the
// node's ID.
var id = xml.id;
if (id) {
var variable = workspace.getVariableById(id);
} else {
var variable = workspace.getVariable(text, type);
}
if (!variable) {
variable = workspace.createVariable(text, type, id);
}
if (type != null && type !== variable.type) {
throw Error('Serialized variable type with id \'' +
variable.getId() + '\' had type ' + variable.type + ', and ' +
'does not match variable field that references it: ' +
Blockly.Xml.domToText(xml) + '.');
}
Blockly.Xml.domToFieldVariable_(workspace, xml, text, field);
} else {
field.setValue(text);
}
field.setValue(text);
};
/**

View File

@@ -44,10 +44,20 @@ function fieldVariable_mockBlock(workspace) {
return {'workspace': workspace, 'isShadow': function(){return false;}};
}
function fieldVariable_createAndInitField(workspace) {
var fieldVariable = new Blockly.FieldVariable('name1');
var mockBlock = fieldVariable_mockBlock(workspace);
fieldVariable.setSourceBlock(mockBlock);
// No view to initialize, but the model still needs work.
fieldVariable.initModel();
return fieldVariable;
}
function test_fieldVariable_Constructor() {
workspace = new Blockly.Workspace();
var fieldVariable = new Blockly.FieldVariable('name1');
assertEquals('name1', fieldVariable.getText());
// The field does not have a variable until after init() is called.
assertEquals('', fieldVariable.getText());
workspace.dispose();
}
@@ -55,52 +65,38 @@ function test_fieldVariable_setValueMatchId() {
// Expect the fieldVariable value to be set to variable name
fieldVariableTestWithMocks_setUp();
workspace.createVariable('name2', null, 'id2');
var fieldVariable = new Blockly.FieldVariable('name1');
var mockBlock = fieldVariable_mockBlock(workspace);
fieldVariable.setSourceBlock(mockBlock);
var fieldVariable = fieldVariable_createAndInitField(workspace);
var oldId = fieldVariable.getValue();
var event = new Blockly.Events.BlockChange(
mockBlock, 'field', undefined, 'name1', 'id2');
fieldVariable.sourceBlock_, 'field', undefined, oldId, 'id2');
setUpMockMethod(mockControl_, Blockly.Events, 'fire', [event], null);
fieldVariable.setValue('id2');
assertEquals('name2', fieldVariable.getText());
assertEquals('id2', fieldVariable.value_);
fieldVariableTestWithMocks_tearDown();
}
function test_fieldVariable_setValueMatchName() {
// Expect the fieldVariable value to be set to variable name
fieldVariableTestWithMocks_setUp();
workspace.createVariable('name2', null, 'id2');
var fieldVariable = new Blockly.FieldVariable('name1');
var mockBlock = fieldVariable_mockBlock(workspace);
fieldVariable.setSourceBlock(mockBlock);
var event = new Blockly.Events.BlockChange(
mockBlock, 'field', undefined, 'name1', 'id2');
setUpMockMethod(mockControl_, Blockly.Events, 'fire', [event], null);
fieldVariable.setValue('name2');
assertEquals('name2', fieldVariable.getText());
assertEquals('id2', fieldVariable.value_);
assertEquals('id2', fieldVariable.getValue());
fieldVariableTestWithMocks_tearDown();
}
function test_fieldVariable_setValueNoVariable() {
// Expect the fieldVariable value to be set to the passed in string. No error
// should be thrown.
fieldVariableTestWithMocks_setUp();
var fieldVariable = new Blockly.FieldVariable('name1');
var mockBlock = {'workspace': workspace,
'isShadow': function(){return false;}};
fieldVariable.setSourceBlock(mockBlock);
var event = new Blockly.Events.BlockChange(
mockBlock, 'field', undefined, 'name1', 'id1');
setUpMockMethod(mockControl_, Blockly.Events, 'fire', [event], null);
fieldVariable.setValue('id1');
assertEquals('id1', fieldVariable.getText());
assertEquals('id1', fieldVariable.value_);
fieldVariableTestWithMocks_tearDown();
var fieldVariable = fieldVariable_createAndInitField(workspace);
var mockBlock = fieldVariable.sourceBlock_;
mockBlock.isShadow = function() {
return false;
};
try {
fieldVariable.setValue('id1');
// Calling setValue with a variable that doesn't exist throws an error.
fail();
} catch (e) {
// expected
} finally {
fieldVariableTestWithMocks_tearDown();
}
}
function test_fieldVariable_dropdownCreateVariablesExist() {
@@ -108,12 +104,12 @@ function test_fieldVariable_dropdownCreateVariablesExist() {
workspace = new Blockly.Workspace();
workspace.createVariable('name1', '', 'id1');
workspace.createVariable('name2', '', 'id2');
var fieldVariable = fieldVariable_createAndInitField(workspace);
var result_options = Blockly.FieldVariable.dropdownCreate.call(
{
'sourceBlock_': {'workspace': workspace},
'getText': function(){return 'name1';},
'getVariableTypes_': function(){return [''];}
});
fieldVariable);
assertEquals(result_options.length, 3);
isEqualArrays(result_options[0], ['name1', 'id1']);
isEqualArrays(result_options[1], ['name2', 'id2']);
@@ -121,77 +117,30 @@ function test_fieldVariable_dropdownCreateVariablesExist() {
workspace.dispose();
}
function test_fieldVariable_dropdownCreateVariablesExist() {
// Expect that the dropdown options will contain the variables that exist.
workspace = new Blockly.Workspace();
workspace.createVariable('name1', '', 'id1');
workspace.createVariable('name2', '', 'id2');
var result_options = Blockly.FieldVariable.dropdownCreate.call(
{
'sourceBlock_': {'workspace': workspace},
'getText': function(){return 'name1';},
'getVariableTypes_': function(){return [''];}
});
assertEquals(result_options.length, 3);
isEqualArrays(result_options[0], ['name1', 'id1']);
isEqualArrays(result_options[1], ['name2', 'id2']);
workspace.dispose();
}
function test_fieldVariable_dropdownVariableAndTypeDoesNotExist() {
// Expect a variable will be created for the selected option. Expect the
// workspace variable map to contain the new variable once.
function test_fieldVariable_setValueNull() {
// This should no longer create a variable for the selected option.
fieldVariableTestWithMocks_setUp();
setUpMockMethod(mockControl_, Blockly.utils, 'genUid', null, ['id1', null]);
var result_options = Blockly.FieldVariable.dropdownCreate.call(
{
'sourceBlock_': {'workspace': workspace},
'getText': function(){return 'name1';},
'getVariableTypes_': function(){return [''];}
});
var fieldVariable = fieldVariable_createAndInitField(workspace);
try {
fieldVariable.setValue(null);
fail();
} catch (e) {
// expected
} finally {
fieldVariableTestWithMocks_tearDown();
}
// Check the options.
assertEquals(2, result_options.length);
isEqualArrays(result_options[0], ['name1', 'id1']);
// Check the variable map.
assertEquals(1, workspace.getAllVariables().length);
checkVariableValues(workspace, 'name1', '', 'id1');
fieldVariableTestWithMocks_tearDown();
}
function test_fieldVariable_dropdownVariableDoesNotExistTypeDoes() {
// Expect a variable will be created for the selected option. Expect the
// workspace variable map to contain the new variable once.
fieldVariableTestWithMocks_setUp();
workspace.createVariable('name1', '', 'id1');
setUpMockMethod(mockControl_, Blockly.utils, 'genUid', null, ['id2', null]);
var result_options = Blockly.FieldVariable.dropdownCreate.call(
{
'sourceBlock_': {'workspace': workspace},
'getText': function(){return 'name2';},
'getVariableTypes_': function(){return [''];}
});
assertEquals(3, result_options.length);
isEqualArrays(result_options[0], ['name1', 'id1']);
isEqualArrays(result_options[1], ['name2', 'id2']);
assertEquals(2, workspace.variableMap_.getAllVariables().length);
checkVariableValues(workspace, 'name1', '', 'id1');
checkVariableValues(workspace, 'name2', '', 'id2');
fieldVariableTestWithMocks_tearDown();
}
function test_fieldVariable_getVariableTypes_undefinedVariableTypes() {
// Expect that since variableTypes is undefined, only type empty string
// will be returned.
// will be returned (regardless of what types are available on the workspace).
workspace = new Blockly.Workspace();
workspace.createVariable('name1', 'type1');
workspace.createVariable('name2', 'type2');
var fieldVariable = new Blockly.FieldVariable('name1');
var resultTypes = fieldVariable.getVariableTypes_();
isEqualArrays(resultTypes, ['']);
@@ -199,12 +148,14 @@ function test_fieldVariable_getVariableTypes_undefinedVariableTypes() {
}
function test_fieldVariable_getVariableTypes_givenVariableTypes() {
// Expect that since variableTypes is undefined, only type empty string
// will be returned.
// Expect that since variableTypes is defined, it will be the return value,
// regardless of what types are available on the workspace.
workspace = new Blockly.Workspace();
workspace.createVariable('name1', 'type1');
workspace.createVariable('name2', 'type2');
var fieldVariable = new Blockly.FieldVariable('name1', null, ['type1', 'type2']);
var fieldVariable = new Blockly.FieldVariable(
'name1', null, ['type1', 'type2']);
var resultTypes = fieldVariable.getVariableTypes_();
isEqualArrays(resultTypes, ['type1', 'type2']);
workspace.dispose();
@@ -212,13 +163,17 @@ function test_fieldVariable_getVariableTypes_givenVariableTypes() {
function test_fieldVariable_getVariableTypes_nullVariableTypes() {
// Expect all variable types to be returned.
// The variable does not need to be initialized to do this--it just needs a
// pointer to the workspace.
workspace = new Blockly.Workspace();
workspace.createVariable('name1', 'type1');
workspace.createVariable('name2', 'type2');
var fieldVariable = new Blockly.FieldVariable('name1');
var mockBlock = fieldVariable_mockBlock(workspace);
fieldVariable.setSourceBlock(mockBlock);
fieldVariable.variableTypes = null;
var resultTypes = fieldVariable.getVariableTypes_();
isEqualArrays(resultTypes, ['type1', 'type2']);
workspace.dispose();
@@ -229,12 +184,15 @@ function test_fieldVariable_getVariableTypes_emptyListVariableTypes() {
workspace = new Blockly.Workspace();
workspace.createVariable('name1', 'type1');
workspace.createVariable('name2', 'type2');
var fieldVariable = new Blockly.FieldVariable('name1');
var mockBlock = fieldVariable_mockBlock(workspace);
fieldVariable.setSourceBlock(mockBlock);
fieldVariable.variableTypes = [];
try {
fieldVariable.getVariableTypes_();
fail();
} catch (e) {
//expected
} finally {

View File

@@ -41,10 +41,10 @@ function proceduresTest_setUpWithMockBlocks() {
'name': 'NAME',
'variable': 'item'
}
],
]
}]);
Blockly.Blocks['procedure_mock_block'].getProcedureDef = function() {
return [this.getFieldValue('NAME'), [], false];
return [this.getField('NAME').getText(), [], false];
};
}
@@ -63,8 +63,9 @@ function test_isNameUsed_NoBlocks() {
function test_isNameUsed_False() {
proceduresTest_setUpWithMockBlocks();
workspace.createVariable('name2', '', 'id2');
var block = new Blockly.Block(workspace, 'procedure_mock_block');
block.setFieldValue('name2', 'NAME');
block.setFieldValue('id2', 'NAME');
var result = Blockly.Procedures.isNameUsed('name1', workspace);
assertFalse(result);
@@ -73,8 +74,9 @@ function test_isNameUsed_False() {
function test_isNameUsed_True() {
proceduresTest_setUpWithMockBlocks();
workspace.createVariable('name1', '', 'id1');
var block = new Blockly.Block(workspace, 'procedure_mock_block');
block.setFieldValue('name1', 'NAME');
block.setFieldValue('id1', 'NAME');
var result = Blockly.Procedures.isNameUsed('name1', workspace);
assertTrue(result);

View File

@@ -89,3 +89,54 @@ function checkVariableValues(container, name, type, id) {
assertEquals(type, variable.type);
assertEquals(id, variable.getId());
}
/**
* Create a test get_var_block.
* Will fail if get_var_block isn't defined.
* TODO (fenichel): Rename to createMockVarBlock.
* @param {!string} variable_id The id of the variable to reference.
* @return {!Blockly.Block} The created block.
*/
function createMockBlock(variable_id) {
if (!Blockly.Blocks['get_var_block']) {
fail();
}
// Turn off events to avoid testing XML at the same time.
Blockly.Events.disable();
var block = new Blockly.Block(workspace, 'get_var_block');
block.inputList[0].fieldRow[0].setValue(variable_id);
Blockly.Events.enable();
return block;
}
function createTwoVariablesAndBlocks(workspace) {
// Create two variables of different types.
workspace.createVariable('name1', 'type1', 'id1');
workspace.createVariable('name2', 'type2', 'id2');
// Create blocks to refer to both of them.
createMockBlock('id1');
createMockBlock('id2');
}
function createVariableAndBlock(workspace) {
workspace.createVariable('name1', 'type1', 'id1');
createMockBlock('id1');
}
function defineGetVarBlock() {
Blockly.defineBlocksWithJsonArray([{
"type": "get_var_block",
"message0": "%1",
"args0": [
{
"type": "field_variable",
"name": "VAR",
"variableTypes": ["", "type1", "type2"]
}
]
}]);
}
function undefineGetVarBlock() {
delete Blockly.Blocks['get_var_block'];
}

View File

@@ -24,38 +24,19 @@ goog.require('goog.testing.MockControl');
var workspace;
var mockControl_;
Blockly.defineBlocksWithJsonArray([{
"type": "get_var_block",
"message0": "%1",
"args0": [
{
"type": "field_variable",
"name": "VAR",
}
]
}]);
function workspaceTest_setUp() {
defineGetVarBlock();
workspace = new Blockly.Workspace();
mockControl_ = new goog.testing.MockControl();
}
function workspaceTest_tearDown() {
undefineGetVarBlock();
mockControl_.$tearDown();
workspace.dispose();
}
/**
* Create a test get_var_block.
* @param {?string} variable_name The string to put into the variable field.
* @return {!Blockly.Block} The created block.
*/
function createMockBlock(variable_name) {
var block = new Blockly.Block(workspace, 'get_var_block');
block.inputList[0].fieldRow[0].setValue(variable_name);
return block;
}
function test_emptyWorkspace() {
workspaceTest_setUp();
try {
@@ -158,107 +139,43 @@ function test_getBlockById() {
function test_deleteVariable_InternalTrivial() {
workspaceTest_setUp();
// TODO (#1199): make a similar test where the variable is given a non-empty
// type.
var var_1 = workspace.createVariable('name1', '', 'id1');
workspace.createVariable('name2', '', 'id2');
createMockBlock('name1');
createMockBlock('name1');
createMockBlock('name2');
var var_1 = workspace.createVariable('name1', 'type1', 'id1');
workspace.createVariable('name2', 'type2', 'id2');
createMockBlock('id1');
createMockBlock('id1');
createMockBlock('id2');
workspace.deleteVariableInternal_(var_1);
var variable = workspace.getVariable('name1', '');
var block_var_name = workspace.topBlocks_[0].getVars()[0];
var uses = workspace.getVariableUsesById(var_1.getId());
workspace.deleteVariableInternal_(var_1, uses);
var variable = workspace.getVariableById('id1');
var block_var_name = workspace.topBlocks_[0].getVarModels()[0].name;
assertNull(variable);
checkVariableValues(workspace, 'name2', '', 'id2');
checkVariableValues(workspace, 'name2', 'type2', 'id2');
assertEquals('name2', block_var_name);
workspaceTest_tearDown();
}
// TODO(marisaleung): Test the alert for deleting a variable that is a procedure.
function test_updateVariableStore_TrivialNoClear() {
workspaceTest_setUp();
workspace.createVariable('name1', 'type1', 'id1');
workspace.createVariable('name2', 'type2', 'id2');
setUpMockMethod(mockControl_, Blockly.Variables, 'allUsedVariables',
[workspace], [['name1', 'name2']]);
try {
workspace.updateVariableStore();
checkVariableValues(workspace, 'name1', 'type1', 'id1');
checkVariableValues(workspace, 'name2', 'type2', 'id2');
} finally {
workspaceTest_tearDown();
}
}
function test_updateVariableStore_NameNotInvariableMap_NoClear() {
workspaceTest_setUp();
setUpMockMethod(mockControl_, Blockly.Variables, 'allUsedVariables',
[workspace], [['name1']]);
setUpMockMethod(mockControl_, Blockly.utils, 'genUid', null, ['1']);
try {
workspace.updateVariableStore();
checkVariableValues(workspace, 'name1', '', '1');
} finally {
workspaceTest_tearDown();
}
}
function test_updateVariableStore_ClearAndAllInUse() {
workspaceTest_setUp();
workspace.createVariable('name1', '', 'id1');
workspace.createVariable('name2', '', 'id2');
// TODO (#1199): make a similar test where the variable is given a non-empty
// type.
// TODO (#1199): get rid of updateVariableStore if possible.
setUpMockMethod(mockControl_, Blockly.Variables, 'allUsedVariables',
[workspace], [['name1', 'name2']]);
try {
workspace.updateVariableStore(true);
checkVariableValues(workspace, 'name1', '', 'id1');
checkVariableValues(workspace, 'name2', '', 'id2');
} finally {
workspaceTest_tearDown();
}
}
function test_updateVariableStore_ClearAndOneInUse() {
workspaceTest_setUp();
workspace.createVariable('name1', '', 'id1');
workspace.createVariable('name2', '', 'id2');
// TODO (#1199): make a similar test where the variable is given a non-empty
// type.
// TODO (#1199): get rid of updateVariableStore if possible.
setUpMockMethod(mockControl_, Blockly.Variables, 'allUsedVariables',
[workspace], [['name1']]);
try {
workspace.updateVariableStore(true);
checkVariableValues(workspace, 'name1', '', 'id1');
var variabe = workspace.getVariable('name2', '');
assertNull(variable);
} finally {
workspaceTest_tearDown();
}
}
function test_addTopBlock_TrivialFlyoutIsTrue() {
workspaceTest_setUp();
workspace.isFlyout = true;
var block = createMockBlock();
workspace.removeTopBlock(block);
setUpMockMethod(mockControl_, Blockly.Variables, 'allUsedVariables', [block],
[['name1']]);
setUpMockMethod(mockControl_, Blockly.utils, 'genUid', null, ['1']);
var targetWorkspace = new Blockly.Workspace();
workspace.targetWorkspace = targetWorkspace;
targetWorkspace.createVariable('name1', '', '1');
// Flyout.init usually does this binding.
workspace.getVariableById =
targetWorkspace.getVariableById.bind(targetWorkspace);
try {
var block = createMockBlock('1');
workspace.removeTopBlock(block);
workspace.addTopBlock(block);
checkVariableValues(workspace, 'name1', '', '1');
} finally {
targetWorkspace.dispose();
workspaceTest_tearDown();
}
}
@@ -297,52 +214,38 @@ function test_clear_NoVariables() {
}
}
function test_renameVariable_NoBlocks() {
// Expect 'renameVariable' to create new variable with newName.
function test_renameVariable_NoReference() {
// Test renaming a variable in the simplest case: when no blocks refer to it.
workspaceTest_setUp();
var id = 'id1';
var type = 'type1';
var oldName = 'name1';
var newName = 'name2';
// Mocked setGroup to ensure only one call to the mocked genUid.
setUpMockMethod(mockControl_, Blockly.Events, 'setGroup', [true, false],
null);
setUpMockMethod(mockControl_, Blockly.utils, 'genUid', null, ['1']);
workspace.createVariable(oldName, type, id);
try {
workspace.renameVariable(oldName, newName);
checkVariableValues(workspace, 'name2', '', '1');
var variable = workspace.getVariable(oldName, '');
assertNull(variable);
workspace.renameVariableById(id, newName);
checkVariableValues(workspace, newName, type, id);
// Renaming should not have created a new variable.
assertEquals(1, workspace.getAllVariables().length);
} finally {
workspaceTest_tearDown();
}
}
function test_renameVariable_SameNameNoBlocks() {
// Expect 'renameVariable' to create new variable with newName.
workspaceTest_setUp();
var name = 'name1';
workspace.createVariable(name, 'type1', 'id1');
workspace.renameVariable(name, name);
checkVariableValues(workspace, name, 'type1', 'id1');
workspaceTest_tearDown();
}
function test_renameVariable_OnlyOldNameBlockExists() {
function test_renameVariable_ReferenceExists() {
// Test renaming a variable when a reference to it exists.
// Expect 'renameVariable' to change oldName variable name to newName.
workspaceTest_setUp();
var oldName = 'name1';
var newName = 'name2';
workspace.createVariable(oldName, '', 'id1');
createMockBlock(oldName);
// TODO (#1199): make a similar test where the variable is given a non-empty
// type.
workspace.renameVariable(oldName, newName);
checkVariableValues(workspace, newName, '', 'id1');
var variable = workspace.getVariable(oldName, '');
var block_var_name = workspace.topBlocks_[0].getVars()[0];
assertNull(variable);
createVariableAndBlock(workspace);
workspace.renameVariableById('id1', newName);
checkVariableValues(workspace, newName, 'type1', 'id1');
// Renaming should not have created a new variable.
assertEquals(1, workspace.getAllVariables().length);
var block_var_name = workspace.topBlocks_[0].getVarModels()[0].name;
assertEquals(newName, block_var_name);
workspaceTest_tearDown();
}
@@ -351,151 +254,120 @@ function test_renameVariable_TwoVariablesSameType() {
// Expect 'renameVariable' to change oldName variable name to newName.
// Expect oldName block name to change to newName
workspaceTest_setUp();
var id1 = 'id1';
var id2 = 'id2';
var type = 'type1';
var oldName = 'name1';
var newName = 'name2';
// TODO (#1199): make a similar test where the variable is given a non-empty
// type.
workspace.createVariable(oldName, '', 'id1');
workspace.createVariable(newName, '', 'id2');
createMockBlock(oldName);
createMockBlock(newName);
// Create two variables of the same type.
workspace.createVariable(oldName, type, id1);
workspace.createVariable(newName, type, id2);
// Create blocks to refer to both of them.
createMockBlock(id1);
createMockBlock(id2);
workspace.renameVariable(oldName, newName);
checkVariableValues(workspace, newName, '', 'id2');
var variable = workspace.getVariable(oldName);
var block_var_name_1 = workspace.topBlocks_[0].getVars()[0];
var block_var_name_2 = workspace.topBlocks_[1].getVars()[0];
workspace.renameVariableById(id1, newName);
checkVariableValues(workspace, newName, type, id2);
// The old variable should have been deleted.
var variable = workspace.getVariableById(id1);
assertNull(variable);
// There should only be one variable left.
assertEquals(1, workspace.getAllVariables().length);
// References should have the correct names.
var block_var_name_1 = workspace.topBlocks_[0].getVarModels()[0].name;
var block_var_name_2 = workspace.topBlocks_[1].getVarModels()[0].name;
assertEquals(newName, block_var_name_1);
assertEquals(newName, block_var_name_2);
workspaceTest_tearDown();
}
function test_renameVariable_TwoVariablesDifferentType() {
// Expect triggered error because of different types
// Expect the rename to succeed, because variables with different types are
// allowed to have the same name.
workspaceTest_setUp();
var oldName = 'name1';
var newName = 'name2';
workspace.createVariable(oldName, 'type1', 'id1');
workspace.createVariable(newName, 'type2', 'id2');
createMockBlock(oldName);
createMockBlock(newName);
createTwoVariablesAndBlocks(workspace);
try {
workspace.renameVariable(oldName, newName);
fail();
} catch (e) {
// expected
}
checkVariableValues(workspace, oldName, 'type1', 'id1');
var newName = 'name2';
workspace.renameVariableById('id1', newName);
checkVariableValues(workspace, newName, 'type1', 'id1');
checkVariableValues(workspace, newName, 'type2', 'id2');
var block_var_name_1 = workspace.topBlocks_[0].getVars()[0];
var block_var_name_2 = workspace.topBlocks_[1].getVars()[0];
assertEquals(oldName, block_var_name_1);
// References shoul have the correct names.
var block_var_name_1 = workspace.topBlocks_[0].getVarModels()[0].name;
var block_var_name_2 = workspace.topBlocks_[1].getVarModels()[0].name;
assertEquals(newName, block_var_name_1);
assertEquals(newName, block_var_name_2);
workspaceTest_tearDown();
}
function test_renameVariable_OldCase() {
// Expect triggered error because of different types
// Rename a variable with a single reference. Update only the capitalization.
workspaceTest_setUp();
var oldCase = 'Name1';
var newName = 'name1';
// TODO (#1199): make a similar test where the variable is given a non-empty
// type.
workspace.createVariable(oldCase, '', 'id1');
createMockBlock(oldCase);
var newName = 'Name1';
workspace.renameVariable(oldCase, newName);
checkVariableValues(workspace, newName, '', 'id1');
var result_oldCase = workspace.getVariable(oldCase, '').name;
assertNotEquals(oldCase, result_oldCase);
createVariableAndBlock(workspace);
workspace.renameVariableById('id1', newName);
checkVariableValues(workspace, newName, 'type1', 'id1');
var variable = workspace.getVariableById('id1');
assertNotEquals('name1', variable.name);
workspaceTest_tearDown();
}
function test_renameVariable_TwoVariablesAndOldCase() {
// Expect triggered error because of different types
// Test renaming a variable to an in-use name, but with different
// capitalization. The new capitalization should apply everywhere.
// TODO (fenichel): What about different capitalization but also different
// types?
workspaceTest_setUp();
var oldName = 'name1';
var oldCase = 'Name2';
var newName = 'name2';
// TODO (#1199): make a similar test where the variable is given a non-empty
// type.
workspace.createVariable(oldName, '', 'id1');
workspace.createVariable(oldCase, '', 'id2');
createMockBlock(oldName);
createMockBlock(oldCase);
workspace.renameVariable(oldName, newName);
var id1 = 'id1';
var id2 = 'id2';
checkVariableValues(workspace, newName, '', 'id2');
var variable = workspace.getVariable(oldName);
var result_oldCase = workspace.getVariable(oldCase).name;
var block_var_name_1 = workspace.topBlocks_[0].getVars()[0];
var block_var_name_2 = workspace.topBlocks_[1].getVars()[0];
var type = 'type1';
workspace.createVariable(oldName, type, id1);
workspace.createVariable(oldCase, type, id2);
createMockBlock(id1);
createMockBlock(id2);
workspace.renameVariableById(id1, newName);
checkVariableValues(workspace, newName, type, id2);
// The old variable should have been deleted.
var variable = workspace.getVariableById(id1);
assertNull(variable);
assertNotEquals(oldCase, result_oldCase);
// There should only be one variable left.
assertEquals(1, workspace.getAllVariables().length);
// Blocks should now use the new capitalization.
var block_var_name_1 = workspace.topBlocks_[0].getVarModels()[0].name;
var block_var_name_2 = workspace.topBlocks_[1].getVarModels()[0].name;
assertEquals(newName, block_var_name_1);
assertEquals(newName, block_var_name_2);
workspaceTest_tearDown();
}
// Extra testing not required for renameVariableById. It calls renameVariable
// and that has extensive testing.
function test_renameVariableById_TwoVariablesSameType() {
// Expect 'renameVariableById' to change oldName variable name to newName.
// Expect oldName block name to change to newName
workspaceTest_setUp();
var oldName = 'name1';
var newName = 'name2';
// TODO (#1199): make a similar test where the variable is given a non-empty
// type.
workspace.createVariable(oldName, '', 'id1');
workspace.createVariable(newName, '', 'id2');
createMockBlock(oldName);
createMockBlock(newName);
workspace.renameVariableById('id1', newName);
checkVariableValues(workspace, newName, '', 'id2');
var variable = workspace.getVariable(oldName);
var block_var_name_1 = workspace.topBlocks_[0].getVars()[0];
var block_var_name_2 = workspace.topBlocks_[1].getVars()[0];
assertNull(variable);
assertEquals(newName, block_var_name_1);
assertEquals(newName, block_var_name_2);
workspaceTest_tearDown();
}
function test_deleteVariable_Trivial() {
workspaceTest_setUp();
// TODO (#1199): make a similar test where the variable is given a non-empty
// type.
workspace.createVariable('name1', '', 'id1');
workspace.createVariable('name2', '', 'id2');
createMockBlock('name1');
createMockBlock('name2');
workspace.deleteVariable('name1', '');
checkVariableValues(workspace, 'name2', '', 'id2');
var variable = workspace.getVariable('name1', '');
var block_var_name = workspace.topBlocks_[0].getVars()[0];
assertNull(variable);
assertEquals('name2', block_var_name);
workspaceTest_tearDown();
}
function test_deleteVariableById_Trivial() {
workspaceTest_setUp();
// TODO (#1199): Make a version of this test that uses different types.
workspace.createVariable('name1', '', 'id1');
workspace.createVariable('name2', '', 'id2');
createMockBlock('name1');
createMockBlock('name2');
createTwoVariablesAndBlocks(workspace);
workspace.deleteVariableById('id1');
checkVariableValues(workspace, 'name2', '', 'id2');
var variable = workspace.getVariable('name1', '');
var block_var_name = workspace.topBlocks_[0].getVars()[0];
checkVariableValues(workspace, 'name2', 'type2', 'id2');
var variable = workspace.getVariableById('id1');
var block_var_name = workspace.topBlocks_[0].getVarModels()[0].name;
assertNull(variable);
assertEquals('name2', block_var_name);
workspaceTest_tearDown();

View File

@@ -33,16 +33,6 @@ goog.require('goog.testing.MockControl');
var workspace;
var mockControl_;
var savedFireFunc = Blockly.Events.fire;
Blockly.defineBlocksWithJsonArray([{
"type": "get_var_block",
"message0": "%1",
"args0": [
{
"type": "field_variable",
"name": "VAR",
}
]
}]);
function temporary_fireEvent(event) {
if (!Blockly.Events.isEnabled()) {
@@ -53,28 +43,19 @@ function temporary_fireEvent(event) {
}
function undoRedoTest_setUp() {
defineGetVarBlock();
workspace = new Blockly.Workspace();
mockControl_ = new goog.testing.MockControl();
Blockly.Events.fire = temporary_fireEvent;
}
function undoRedoTest_tearDown() {
undefineGetVarBlock();
mockControl_.$tearDown();
workspace.dispose();
Blockly.Events.fire = savedFireFunc;
}
/**
* Create a test get_var_block.
* @param {string} variableName The string to put into the variable field.
* @return {!Blockly.Block} The created block.
*/
function createMockBlock(variableName) {
var block = new Blockly.Block(workspace, 'get_var_block');
block.inputList[0].fieldRow[0].setValue(variableName);
return block;
}
/**
* Check that the top block with the given index contains a variable with
* the given name.
@@ -82,7 +63,7 @@ function createMockBlock(variableName) {
* @param {string} name The expected name of the variable in the block.
*/
function undoRedoTest_checkBlockVariableName(blockIndex, name) {
var blockVarName = workspace.topBlocks_[blockIndex].getVars()[0];
var blockVarName = workspace.topBlocks_[blockIndex].getVarModels()[0].name;
assertEquals(name, blockVarName);
}
@@ -91,10 +72,14 @@ function createTwoVarsEmptyType() {
workspace.createVariable('name2', '', 'id2');
}
function test_undoCreateVariable_Trivial() {
undoRedoTest_setUp();
function createTwoVarsDifferentTypes() {
workspace.createVariable('name1', 'type1', 'id1');
workspace.createVariable('name2', 'type2', 'id2');
}
function test_undoCreateVariable_Trivial() {
undoRedoTest_setUp();
createTwoVarsDifferentTypes();
workspace.undo();
checkVariableValues(workspace, 'name1', 'type1', 'id1');
@@ -107,8 +92,7 @@ function test_undoCreateVariable_Trivial() {
function test_redoAndUndoCreateVariable_Trivial() {
undoRedoTest_setUp();
workspace.createVariable('name1', 'type1', 'id1');
workspace.createVariable('name2', 'type2', 'id2');
createTwoVarsDifferentTypes();
workspace.undo();
workspace.undo(true);
@@ -129,8 +113,7 @@ function test_redoAndUndoCreateVariable_Trivial() {
function test_undoDeleteVariable_NoBlocks() {
undoRedoTest_setUp();
workspace.createVariable('name1', 'type1', 'id1');
workspace.createVariable('name2', 'type2', 'id2');
createTwoVarsDifferentTypes();
workspace.deleteVariableById('id1');
workspace.deleteVariableById('id2');
@@ -146,32 +129,30 @@ function test_undoDeleteVariable_NoBlocks() {
function test_undoDeleteVariable_WithBlocks() {
undoRedoTest_setUp();
// TODO (#1199): make a similar test where the variable is given a non-empty
// type.
workspace.createVariable('name1', '', 'id1');
workspace.createVariable('name2', '', 'id2');
createMockBlock('name1');
createMockBlock('name2');
createTwoVariablesAndBlocks(workspace);
workspace.deleteVariableById('id1');
workspace.deleteVariableById('id2');
workspace.undo();
undoRedoTest_checkBlockVariableName(0, 'name2');
assertNull(workspace.getVariableById('id1'));
checkVariableValues(workspace, 'name2', '', 'id2');
checkVariableValues(workspace, 'name2', 'type2', 'id2');
workspace.undo();
undoRedoTest_checkBlockVariableName(0, 'name2');
undoRedoTest_checkBlockVariableName(1, 'name1');
checkVariableValues(workspace, 'name1', '', 'id1');
checkVariableValues(workspace, 'name2', '', 'id2');
checkVariableValues(workspace, 'name1', 'type1', 'id1');
checkVariableValues(workspace, 'name2', 'type2', 'id2');
undoRedoTest_tearDown();
}
function test_redoAndUndoDeleteVariable_NoBlocks() {
undoRedoTest_setUp();
workspace.createVariable('name1', 'type1', 'id1');
workspace.createVariable('name2', 'type2', 'id2');
createTwoVarsDifferentTypes();
workspace.deleteVariableById('id1');
workspace.deleteVariableById('id2');
@@ -192,12 +173,9 @@ function test_redoAndUndoDeleteVariable_NoBlocks() {
function test_redoAndUndoDeleteVariable_WithBlocks() {
undoRedoTest_setUp();
// TODO (#1199): make a similar test where the variable is given a non-empty
// type.
workspace.createVariable('name1', '', 'id1');
workspace.createVariable('name2', '', 'id2');
createMockBlock('name1');
createMockBlock('name2');
createTwoVariablesAndBlocks(workspace);
workspace.deleteVariableById('id1');
workspace.deleteVariableById('id2');
@@ -214,7 +192,7 @@ function test_redoAndUndoDeleteVariable_WithBlocks() {
// Expect that variable 'id2' is recreated
undoRedoTest_checkBlockVariableName(0, 'name2');
assertNull(workspace.getVariableById('id1'));
checkVariableValues(workspace, 'name2', '', 'id2');
checkVariableValues(workspace, 'name2', 'type2', 'id2');
undoRedoTest_tearDown();
}
@@ -245,12 +223,11 @@ function test_redoAndUndoDeleteVariableTwice_NoBlocks() {
function test_redoAndUndoDeleteVariableTwice_WithBlocks() {
undoRedoTest_setUp();
// TODO (#1199): make a similar test where the variable is given a non-empty
// type.
workspace.createVariable('name1', '', 'id1');
createMockBlock('name1');
workspace.deleteVariableById('id1');
workspace.deleteVariableById('id1');
var id = 'id1';
workspace.createVariable('name1', 'type1', id);
createMockBlock(id);
workspace.deleteVariableById(id);
workspace.deleteVariableById(id);
// Check the undoStack only recorded one delete event.
var undoStack = workspace.undoStack_;
@@ -261,45 +238,27 @@ function test_redoAndUndoDeleteVariableTwice_WithBlocks() {
// undo delete
workspace.undo();
undoRedoTest_checkBlockVariableName(0, 'name1');
checkVariableValues(workspace, 'name1', '', 'id1');
checkVariableValues(workspace, 'name1', 'type1', id);
// redo delete
workspace.undo(true);
assertEquals(0, workspace.topBlocks_.length);
assertNull(workspace.getVariableById('id1'));
assertNull(workspace.getVariableById(id));
// redo delete, nothing should happen
workspace.undo(true);
assertEquals(0, workspace.topBlocks_.length);
assertNull(workspace.getVariableById('id1'));
undoRedoTest_tearDown();
}
function test_undoRedoRenameVariable_NeitherVariableExists() {
// Expect that a variable with the name, 'name2', and the generated UUID,
// 'id2', to be created when rename is called. Undo removes this variable
// and redo recreates it.
undoRedoTest_setUp();
setUpMockMethod(mockControl_, Blockly.utils, 'genUid', null,
['rename_group', 'id2', 'delete_group']);
workspace.renameVariable('name1', 'name2');
workspace.undo();
assertNull(workspace.getVariableById('id2'));
workspace.undo(true);
checkVariableValues(workspace, 'name2', '', 'id2');
assertNull(workspace.getVariableById(id));
undoRedoTest_tearDown();
}
function test_undoRedoRenameVariable_OneExists_NoBlocks() {
undoRedoTest_setUp();
workspace.createVariable('name1', '', 'id1');
workspace.renameVariable('name1', 'name2');
workspace.renameVariableById('id1', 'name2');
workspace.undo();
checkVariableValues(workspace, 'name1', '', 'id1');
assertNull(workspace.getVariable('name2'));
workspace.undo(true);
checkVariableValues(workspace, 'name2', '', 'id1');
@@ -309,13 +268,12 @@ function test_undoRedoRenameVariable_OneExists_NoBlocks() {
function test_undoRedoRenameVariable_OneExists_WithBlocks() {
undoRedoTest_setUp();
workspace.createVariable('name1', '', 'id1');
createMockBlock('name1');
workspace.renameVariable('name1', 'name2');
createMockBlock('id1');
workspace.renameVariableById('id1', 'name2');
workspace.undo();
undoRedoTest_checkBlockVariableName(0, 'name1');
checkVariableValues(workspace, 'name1', '', 'id1');
assertNull(workspace.getVariable('name2'));
workspace.undo(true);
checkVariableValues(workspace, 'name2', '', 'id1');
@@ -326,7 +284,7 @@ function test_undoRedoRenameVariable_OneExists_WithBlocks() {
function test_undoRedoRenameVariable_BothExist_NoBlocks() {
undoRedoTest_setUp();
createTwoVarsEmptyType();
workspace.renameVariable('name1', 'name2');
workspace.renameVariableById('id1', 'name2');
workspace.undo();
checkVariableValues(workspace, 'name1', '', 'id1');
@@ -334,16 +292,16 @@ function test_undoRedoRenameVariable_BothExist_NoBlocks() {
workspace.undo(true);
checkVariableValues(workspace, 'name2', '', 'id2');
assertNull(workspace.getVariable('name1'));
assertNull(workspace.getVariableById('id1'));
undoRedoTest_tearDown();
}
function test_undoRedoRenameVariable_BothExist_WithBlocks() {
undoRedoTest_setUp();
createTwoVarsEmptyType();
createMockBlock('name1');
createMockBlock('name2');
workspace.renameVariable('name1', 'name2');
createMockBlock('id1');
createMockBlock('id2');
workspace.renameVariableById('id1', 'name2');
workspace.undo();
undoRedoTest_checkBlockVariableName(0, 'name1');
@@ -360,7 +318,7 @@ function test_undoRedoRenameVariable_BothExist_WithBlocks() {
function test_undoRedoRenameVariable_BothExistCaseChange_NoBlocks() {
undoRedoTest_setUp();
createTwoVarsEmptyType();
workspace.renameVariable('name1', 'Name2');
workspace.renameVariableById('id1', 'Name2');
workspace.undo();
checkVariableValues(workspace, 'name1', '', 'id1');
@@ -375,9 +333,9 @@ function test_undoRedoRenameVariable_BothExistCaseChange_NoBlocks() {
function test_undoRedoRenameVariable_BothExistCaseChange_WithBlocks() {
undoRedoTest_setUp();
createTwoVarsEmptyType();
createMockBlock('name1');
createMockBlock('name2');
workspace.renameVariable('name1', 'Name2');
createMockBlock('id1');
createMockBlock('id2');
workspace.renameVariableById('id1', 'Name2');
workspace.undo();
undoRedoTest_checkBlockVariableName(0, 'name1');
@@ -387,7 +345,7 @@ function test_undoRedoRenameVariable_BothExistCaseChange_WithBlocks() {
workspace.undo(true);
checkVariableValues(workspace, 'Name2', '', 'id2');
assertNull(workspace.getVariable('name1'));
assertNull(workspace.getVariableById('id1'));
undoRedoTest_checkBlockVariableName(0, 'Name2');
undoRedoTest_checkBlockVariableName(1, 'Name2');
undoRedoTest_tearDown();
@@ -396,7 +354,7 @@ function test_undoRedoRenameVariable_BothExistCaseChange_WithBlocks() {
function test_undoRedoRenameVariable_OnlyCaseChange_NoBlocks() {
undoRedoTest_setUp();
workspace.createVariable('name1', '', 'id1');
workspace.renameVariable('name1', 'Name1');
workspace.renameVariableById('id1', 'Name1');
workspace.undo();
checkVariableValues(workspace, 'name1', '', 'id1');
@@ -409,8 +367,8 @@ function test_undoRedoRenameVariable_OnlyCaseChange_NoBlocks() {
function test_undoRedoRenameVariable_OnlyCaseChange_WithBlocks() {
undoRedoTest_setUp();
workspace.createVariable('name1', '', 'id1');
createMockBlock('name1');
workspace.renameVariable('name1', 'Name1');
createMockBlock('id1');
workspace.renameVariableById('id1', 'Name1');
workspace.undo();
undoRedoTest_checkBlockVariableName(0, 'name1');

View File

@@ -286,10 +286,10 @@ function test_appendDomToWorkspace() {
function test_blockToDom_fieldToDom_trivial() {
xmlTest_setUpWithMockBlocks();
// TODO (#1199): make a similar test where the variable is given a non-empty
// type.
// type.f
workspace.createVariable('name1', '', 'id1');
var block = new Blockly.Block(workspace, 'field_variable_test_block');
block.inputList[0].fieldRow[0].setValue('name1');
block.inputList[0].fieldRow[0].setValue('id1');
var resultFieldDom = Blockly.Xml.blockToDom(block).childNodes[0];
xmlTest_checkVariableFieldDomValues(resultFieldDom, 'VAR', '', 'id1',
'name1');
@@ -301,7 +301,7 @@ function test_blockToDom_fieldToDom_defaultCase() {
setUpMockMethod(mockControl_, Blockly.utils, 'genUid', null, ['1', '1']);
workspace.createVariable('name1');
var block = new Blockly.Block(workspace, 'field_variable_test_block');
block.inputList[0].fieldRow[0].setValue('name1');
block.inputList[0].fieldRow[0].setValue('1');
var resultFieldDom = Blockly.Xml.blockToDom(block).childNodes[0];
// Expect type is '' and id is '1' since we don't specify type and id.
xmlTest_checkVariableFieldDomValues(resultFieldDom, 'VAR', '', '1', 'name1');
@@ -345,14 +345,14 @@ function test_variablesToDom_oneVariable() {
function test_variablesToDom_twoVariables_oneBlock() {
xmlTest_setUpWithMockBlocks();
workspace.createVariable('name1', 'type1', 'id1');
workspace.createVariable('name1', '', 'id1');
workspace.createVariable('name2', 'type2', 'id2');
var block = new Blockly.Block(workspace, 'field_variable_test_block');
block.inputList[0].fieldRow[0].setValue('name1');
block.inputList[0].fieldRow[0].setValue('id1');
var resultDom = Blockly.Xml.variablesToDom(workspace.getAllVariables());
assertEquals(2, resultDom.children.length);
xmlTest_checkVariableDomValues(resultDom.children[0], 'type1', 'id1',
xmlTest_checkVariableDomValues(resultDom.children[0], '', 'id1',
'name1');
xmlTest_checkVariableDomValues(resultDom.children[1], 'type2', 'id2',
'name2');
@@ -380,14 +380,12 @@ function test_variableFieldXml_caseSensitive() {
}
};
var generatedXml = Blockly.Variables.generateVariableFieldXml_(mockVariableModel);
// The field contains this XML tag as a result of how we're generating this
// XML. This is not desirable, but the goal of this test is to make sure
// we're preserving case-sensitivity.
var xmlns = 'xmlns="http://www.w3.org/1999/xhtml"';
var generatedXml =
Blockly.Variables.generateVariableFieldXml_(mockVariableModel);
var goldenXml =
'<field ' + xmlns + ' name="VAR"' +
'<field name="VAR"' +
' id="' + id + '"' +
' variabletype="' + type + '"' +
' id="' + id + '">' + name + '</field>';
'>' + name + '</field>';
assertEquals(goldenXml, generatedXml);
}