chore: delete procedure data models (#6796)

* chore: delete procedure data models

* chore: remove and fix procedure model related tests
This commit is contained in:
Beka Westberg
2023-01-26 09:35:08 -08:00
committed by GitHub
parent da9ae8f472
commit 5bbb39972a
7 changed files with 118 additions and 634 deletions

View File

@@ -30,8 +30,6 @@ import {IProcedureMap} from './interfaces/i_procedure_map.js';
import {IProcedureModel} from './interfaces/i_procedure_model.js';
import {IProcedureBlock, isProcedureBlock} from './interfaces/i_procedure_block.js';
import {ObservableProcedureMap} from './procedures/observable_procedure_map.js';
import {ObservableProcedureModel} from './procedures/observable_procedure_model.js';
import {ObservableParameterModel} from './procedures/observable_parameter_model.js';
import {triggerProceduresUpdate} from './procedures/update_procedures.js';
import * as utilsXml from './utils/xml.js';
import * as Variables from './variables.js';
@@ -502,8 +500,6 @@ export function getDefinition(name: string, workspace: Workspace): Block|null {
export {
ObservableProcedureMap,
ObservableProcedureModel,
ObservableParameterModel,
triggerProceduresUpdate,
IParameterModel,
IProcedureBlock,

View File

@@ -1,113 +0,0 @@
/**
* @license
* Copyright 2022 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import * as eventUtils from '../events/utils.js';
import {genUid} from '../utils/idgenerator.js';
import type {IParameterModel} from '../interfaces/i_parameter_model.js';
import type {IProcedureModel} from '../interfaces/i_procedure_model';
import {triggerProceduresUpdate} from './update_procedures.js';
import type {VariableModel} from '../variable_model.js';
import type {Workspace} from '../workspace.js';
import * as goog from '../../closure/goog/goog.js';
goog.declareModuleId('Blockly.procedures.ObservableParameterModel');
export class ObservableParameterModel implements IParameterModel {
private id: string;
private variable: VariableModel;
private shouldFireEvents = false;
private procedureModel: IProcedureModel|null = null;
constructor(
private readonly workspace: Workspace, name: string, id?: string,
varId?: string) {
this.id = id ?? genUid();
this.variable = this.workspace.getVariable(name) ??
workspace.createVariable(name, '', varId);
}
/**
* Sets the name of this parameter to the given name.
*/
setName(name: string): this {
if (name === this.variable.name) return this;
const oldName = this.variable.name;
this.variable =
this.workspace.getVariable(name) ?? this.workspace.createVariable(name);
triggerProceduresUpdate(this.workspace);
return this;
}
/**
* Unimplemented. The built-in ParameterModel does not support typing.
* If you want your procedure blocks to have typed parameters, you need to
* implement your own ParameterModel.
*
* @throws Throws for the ObservableParameterModel specifically because this
* method is unimplemented.
*/
setTypes(_types: string[]): this {
throw new Error(
'The built-in ParameterModel does not support typing. You need to ' +
'implement your own custom ParameterModel.');
}
/**
* Returns the name of this parameter.
*/
getName(): string {
return this.variable.name;
}
/**
* Returns the types of this parameter.
*/
getTypes(): string[] {
return [];
}
/**
* Returns the unique language-neutral ID for the parameter.
*
* This represents the identify of the variable model which does not change
* over time.
*/
getId(): string {
return this.id;
}
/** Returns the variable model associated with the parameter model. */
getVariableModel(): VariableModel {
return this.variable;
}
/**
* Tells the parameter model it should fire events.
*
* @internal
*/
startPublishing() {
this.shouldFireEvents = true;
}
/**
* Tells the parameter model it should not fire events.
*
* @internal
*/
stopPublishing() {
this.shouldFireEvents = false;
}
/** Sets the procedure model this parameter is a part of. */
setProcedureModel(model: IProcedureModel): this {
// TODO: Not sure if we want to do this, or accept it via the constructor.
// That means it could be non-null, but it would also break the fluent
// API.
this.procedureModel = model;
return this;
}
}

View File

@@ -1,195 +0,0 @@
/**
* @license
* Copyright 2022 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import * as eventUtils from '../events/utils.js';
import {genUid} from '../utils/idgenerator.js';
import type {IParameterModel} from '../interfaces/i_parameter_model.js';
import type {IProcedureModel} from '../interfaces/i_procedure_model.js';
import {isObservable} from '../interfaces/i_observable.js';
import {triggerProceduresUpdate} from './update_procedures.js';
import type {Workspace} from '../workspace.js';
import * as goog from '../../closure/goog/goog.js';
goog.declareModuleId('Blockly.procedures.ObservableProcedureModel');
export class ObservableProcedureModel implements IProcedureModel {
private id: string;
private name: string;
private parameters: IParameterModel[] = [];
private returnTypes: string[]|null = null;
private enabled = true;
private shouldFireEvents = false;
private shouldTriggerUpdates = true;
constructor(
private readonly workspace: Workspace, name: string, id?: string) {
this.id = id ?? genUid();
this.name = name;
}
/** Sets the human-readable name of the procedure. */
setName(name: string): this {
if (name === this.name) return this;
const prevName = this.name;
this.name = name;
if (this.shouldTriggerUpdates) triggerProceduresUpdate(this.workspace);
return this;
}
/**
* Inserts a parameter into the list of parameters.
*
* To move a parameter, first delete it, and then re-insert.
*/
insertParameter(parameterModel: IParameterModel, index: number): this {
if (this.parameters[index] &&
this.parameters[index].getId() === parameterModel.getId()) {
return this;
}
this.parameters.splice(index, 0, parameterModel);
parameterModel.setProcedureModel(this);
if (isObservable(parameterModel)) {
if (this.shouldFireEvents) {
parameterModel.startPublishing();
} else {
parameterModel.stopPublishing();
}
}
if (this.shouldTriggerUpdates) triggerProceduresUpdate(this.workspace);
return this;
}
/** Removes the parameter at the given index from the parameter list. */
deleteParameter(index: number): this {
if (!this.parameters[index]) return this;
const oldParam = this.parameters[index];
this.parameters.splice(index, 1);
if (this.shouldTriggerUpdates) triggerProceduresUpdate(this.workspace);
if (isObservable(oldParam)) {
oldParam.stopPublishing();
}
return this;
}
/**
* Sets whether the procedure has a return value (empty array) or no return
* value (null).
*
* The built-in procedure model does not support procedures that have actual
* return types (i.e. non-empty arrays, e.g. ['number']). If you want your
* procedure block to have return types, you need to implement your own
* procedure model.
*/
setReturnTypes(types: string[]|null): this {
if (types && types.length) {
throw new Error(
'The built-in ProcedureModel does not support typing. You need to ' +
'implement your own custom ProcedureModel.');
}
// Either they're both an empty array, or both null. Noop either way.
if (!!types === !!this.returnTypes) return this;
const oldReturnTypes = this.returnTypes;
this.returnTypes = types;
if (this.shouldTriggerUpdates) triggerProceduresUpdate(this.workspace);
return this;
}
/**
* Sets whether this procedure is enabled/disabled. If a procedure is disabled
* all procedure caller blocks should be disabled as well.
*/
setEnabled(enabled: boolean): this {
if (enabled === this.enabled) return this;
this.enabled = enabled;
if (this.shouldTriggerUpdates) triggerProceduresUpdate(this.workspace);
return this;
}
/**
* Disables triggering updates to procedure blocks until the endBulkUpdate
* is called.
*
* @internal
*/
startBulkUpdate() {
this.shouldTriggerUpdates = false;
}
/**
* Triggers an update to procedure blocks. Should be used with
* startBulkUpdate.
*
* @internal
*/
endBulkUpdate() {
this.shouldTriggerUpdates = true;
triggerProceduresUpdate(this.workspace);
}
/** Returns the unique language-neutral ID for the procedure. */
getId(): string {
return this.id;
}
/** Returns the human-readable name of the procedure. */
getName(): string {
return this.name;
}
/** Returns the parameter at the given index in the parameter list. */
getParameter(index: number): IParameterModel {
return this.parameters[index];
}
/** Returns an array of all of the parameters in the parameter list. */
getParameters(): IParameterModel[] {
return [...this.parameters];
}
/**
* Returns the return type of the procedure.
*
* Null represents a procedure that does not return a value.
*/
getReturnTypes(): string[]|null {
return this.returnTypes;
}
/**
* Returns whether the procedure is enabled/disabled. If a procedure is
* disabled, all procedure caller blocks should be disabled as well.
*/
getEnabled(): boolean {
return this.enabled;
}
/**
* Tells the procedure model it should fire events.
*
* @internal
*/
startPublishing() {
this.shouldFireEvents = true;
for (const param of this.parameters) {
if (isObservable(param)) param.startPublishing();
}
}
/**
* Tells the procedure model it should not fire events.
*
* @internal
*/
stopPublishing() {
this.shouldFireEvents = false;
for (const param of this.parameters) {
if (isObservable(param)) param.stopPublishing();
}
}
}

View File

@@ -7,10 +7,7 @@
import {IParameterModel} from '../interfaces/i_parameter_model.js';
import {IProcedureModel} from '../interfaces/i_procedure_model.js';
import type {ISerializer} from '../interfaces/i_serializer.js';
import {ObservableProcedureModel} from '../procedures/observable_procedure_model.js';
import {ObservableParameterModel} from '../procedures/observable_parameter_model.js';
import * as priorities from './priorities.js';
import * as serializationRegistry from './registry.js';
import type {Workspace} from '../workspace.js';
@@ -163,12 +160,3 @@ export class ProcedureSerializer<ProcedureModel extends IProcedureModel,
workspace.getProcedureMap().clear();
}
}
/**
* A ProcedureSerializer that deserializes to create the built-in
* ObservableProcedureModels and ObservableParameterModels.
*/
export const observableProcedureSerializer =
new ProcedureSerializer(ObservableProcedureModel, ObservableParameterModel);
serializationRegistry.register('procedures', observableProcedureSerializer);

View File

@@ -9,6 +9,7 @@ goog.declareModuleId('Blockly.test.jsoSerialization');
import * as Blockly from '../../build/src/core/blockly.js';
import {createGenUidStubWithReturns, sharedTestSetup, sharedTestTeardown, workspaceTeardown} from './test_helpers/setup_teardown.js';
import {defineRowBlock, defineStackBlock, defineStatementBlock} from './test_helpers/block_definitions.js';
import {MockParameterModel, MockProcedureModel} from './test_helpers/procedures.js';
suite('JSO Serialization', function() {
@@ -796,125 +797,40 @@ suite('JSO Serialization', function() {
});
suite('Procedures', function() {
class MockProcedureModel {
constructor() {
this.id = Blockly.utils.idGenerator.genUid();
this.name = '';
this.parameters = [];
this.returnTypes = null;
this.enabled = true;
}
setName(name) {
this.name = name;
return this;
}
insertParameter(parameterModel, index) {
this.parameters.splice(index, 0, parameterModel);
return this;
}
deleteParameter(index) {
this.parameters.splice(index, 1);
return this;
}
setReturnTypes(types) {
this.returnTypes = types;
return this;
}
setEnabled(enabled) {
this.enabled = enabled;
return this;
}
getId() {
return this.id;
}
getName() {
return this.name;
}
getParameter(index) {
return this.parameters[index];
}
getParameters() {
return [...this.parameters];
}
getReturnTypes() {
return this.returnTypes;
}
getEnabled() {
return this.enabled;
}
}
class MockParameterModel {
constructor(name) {
this.id = Blockly.utils.idGenerator.genUid();
this.name = name;
this.types = [];
}
setName(name) {
this.name = name;
return this;
}
setTypes(types) {
this.types = types;
return this;
}
getName() {
return this.name;
}
getTypes() {
return this.types;
}
getId() {
return this.id;
}
}
setup(function() {
this.procedureMap = this.workspace.getProcedureMap();
this.serializer =
new Blockly.serialization.procedures.ProcedureSerializer(
MockProcedureModel, MockParameterModel);
});
teardown(function() {
this.procedureMap = null;
this.serializer = null;
});
suite('invariant properties', function() {
test('the state always has an id property', function() {
const procedureModel = new MockProcedureModel();
this.procedureMap.add(procedureModel);
const jso = Blockly.serialization.workspaces.save(this.workspace);
const procedure = jso['procedures'][0];
const jso = this.serializer.save(this.workspace);
const procedure = jso[0];
assertProperty(procedure, 'id', procedureModel.getId());
});
test('if the name has not been set, name is an empty string', function() {
const procedureModel = new MockProcedureModel();
this.procedureMap.add(procedureModel);
const jso = Blockly.serialization.workspaces.save(this.workspace);
const procedure = jso['procedures'][0];
const jso = this.serializer.save(this.workspace);
const procedure = jso[0];
assertProperty(procedure, 'name', '');
});
test('if the name has been set, name is the string', function() {
const procedureModel = new MockProcedureModel().setName('testName');
this.procedureMap.add(procedureModel);
const jso = Blockly.serialization.workspaces.save(this.workspace);
const procedure = jso['procedures'][0];
const jso = this.serializer.save(this.workspace);
const procedure = jso[0];
assertProperty(procedure, 'name', 'testName');
});
});
@@ -923,8 +839,8 @@ suite('JSO Serialization', function() {
test('if the procedure does not return, returnTypes is null', function() {
const procedureModel = new MockProcedureModel();
this.procedureMap.add(procedureModel);
const jso = Blockly.serialization.workspaces.save(this.workspace);
const procedure = jso['procedures'][0];
const jso = this.serializer.save(this.workspace);
const procedure = jso[0];
assertProperty(procedure, 'returnTypes', null);
});
@@ -933,8 +849,8 @@ suite('JSO Serialization', function() {
function() {
const procedureModel = new MockProcedureModel().setReturnTypes([]);
this.procedureMap.add(procedureModel);
const jso = Blockly.serialization.workspaces.save(this.workspace);
const procedure = jso['procedures'][0];
const jso = this.serializer.save(this.workspace);
const procedure = jso[0];
assertProperty(procedure, 'returnTypes', []);
});
@@ -944,8 +860,8 @@ suite('JSO Serialization', function() {
const procedureModel = new MockProcedureModel()
.setReturnTypes(['a type']);
this.procedureMap.add(procedureModel);
const jso = Blockly.serialization.workspaces.save(this.workspace);
const procedure = jso['procedures'][0];
const jso = this.serializer.save(this.workspace);
const procedure = jso[0];
assertProperty(procedure, 'returnTypes', ['a type']);
});
});
@@ -956,8 +872,8 @@ suite('JSO Serialization', function() {
const parameterModel = new MockParameterModel('testparam');
this.procedureMap.add(
new MockProcedureModel().insertParameter(parameterModel, 0));
const jso = Blockly.serialization.workspaces.save(this.workspace);
const parameter = jso['procedures'][0]['parameters'][0];
const jso = this.serializer.save(this.workspace);
const parameter = jso[0]['parameters'][0];
assertProperty(parameter, 'id', parameterModel.getId());
});
@@ -965,8 +881,8 @@ suite('JSO Serialization', function() {
const parameterModel = new MockParameterModel('testparam');
this.procedureMap.add(
new MockProcedureModel().insertParameter(parameterModel, 0));
const jso = Blockly.serialization.workspaces.save(this.workspace);
const parameter = jso['procedures'][0]['parameters'][0];
const jso = this.serializer.save(this.workspace);
const parameter = jso[0]['parameters'][0];
assertProperty(parameter, 'name', 'testparam');
});
});
@@ -978,8 +894,8 @@ suite('JSO Serialization', function() {
const parameterModel = new MockParameterModel('testparam');
this.procedureMap.add(
new MockProcedureModel().insertParameter(parameterModel, 0));
const jso = Blockly.serialization.workspaces.save(this.workspace);
const parameter = jso['procedures'][0]['parameters'][0];
const jso = this.serializer.save(this.workspace);
const parameter = jso[0]['parameters'][0];
assertNoProperty(parameter, 'types');
});
@@ -988,8 +904,8 @@ suite('JSO Serialization', function() {
new MockParameterModel('testparam').setTypes(['a type']);
this.procedureMap.add(
new MockProcedureModel().insertParameter(parameterModel, 0));
const jso = Blockly.serialization.workspaces.save(this.workspace);
const parameter = jso['procedures'][0]['parameters'][0];
const jso = this.serializer.save(this.workspace);
const parameter = jso[0]['parameters'][0];
assertProperty(parameter, 'types', ['a type']);
});
});

View File

@@ -6,6 +6,7 @@
import {sharedTestSetup, sharedTestTeardown} from './test_helpers/setup_teardown.js';
import {assertEventFiredShallow, assertEventNotFired, createChangeListenerSpy} from './test_helpers/events.js';
import {MockProcedureModel} from './test_helpers/procedures.js';
goog.declareModuleId('Blockly.test.procedureMap');
@@ -40,9 +41,7 @@ suite('Procedure Map', function() {
suite('procedure map updates', function() {
test('inserting a procedure does not trigger an update', function() {
const procedureModel =
new Blockly.procedures.ObservableProcedureModel(
this.workspace, 'test name');
const procedureModel = new MockProcedureModel();
this.procedureMap.set(procedureModel.getId(), procedureModel);
chai.assert.isFalse(
@@ -50,18 +49,14 @@ suite('Procedure Map', function() {
});
test('adding a procedure does not trigger an update', function() {
this.procedureMap.add(
new Blockly.procedures.ObservableProcedureModel(
this.workspace, 'test name'));
this.procedureMap.add(new MockProcedureModel());
chai.assert.isFalse(
this.updateSpy.called, 'Expected no update to be triggered');
});
test('deleting a procedure triggers an update', function() {
const procedureModel =
new Blockly.procedures.ObservableProcedureModel(
this.workspace, 'test name');
const procedureModel = new MockProcedureModel();
this.procedureMap.add(procedureModel);
this.procedureMap.delete(procedureModel.getId());
@@ -70,197 +65,5 @@ suite('Procedure Map', function() {
this.updateSpy.calledOnce, 'Expected an update to be triggered');
});
});
suite('procedure model updates', function() {
test('setting the name triggers an update', function() {
const procedureModel =
new Blockly.procedures.ObservableProcedureModel(
this.workspace, 'test name');
this.procedureMap.add(procedureModel);
procedureModel.setName('new name');
chai.assert.isTrue(
this.updateSpy.calledOnce, 'Expected an update to be triggered');
});
test('setting the return type triggers an update', function() {
const procedureModel =
new Blockly.procedures.ObservableProcedureModel(
this.workspace, 'test name');
this.procedureMap.add(procedureModel);
procedureModel.setReturnTypes([]);
chai.assert.isTrue(
this.updateSpy.calledOnce, 'Expected an update to be triggered');
});
test('removing the return type triggers an update', function() {
const procedureModel =
new Blockly.procedures.ObservableProcedureModel(
this.workspace, 'test name')
.setReturnTypes([]);
this.procedureMap.add(procedureModel);
this.updateSpy.resetHistory();
procedureModel.setReturnTypes(null);
chai.assert.isTrue(
this.updateSpy.calledOnce, 'Expected an update to be triggered');
});
test('disabling the procedure triggers an update', function() {
const procedureModel =
new Blockly.procedures.ObservableProcedureModel(
this.workspace, 'test name');
this.procedureMap.add(procedureModel);
procedureModel.setEnabled(false);
chai.assert.isTrue(
this.updateSpy.calledOnce, 'Expected an update to be triggered');
});
test('enabling the procedure triggers an update', function() {
const procedureModel =
new Blockly.procedures.ObservableProcedureModel(
this.workspace, 'test name')
.setEnabled(false);
this.procedureMap.add(procedureModel);
this.updateSpy.resetHistory();
procedureModel.setEnabled(true);
chai.assert.isTrue(
this.updateSpy.calledOnce, 'Expected an update to be triggered');
});
test('inserting a parameter triggers an update', function() {
const procedureModel =
new Blockly.procedures.ObservableProcedureModel(
this.workspace, 'test name');
this.procedureMap.add(procedureModel);
procedureModel.insertParameter(
new Blockly.procedures.ObservableParameterModel(this.workspace));
chai.assert.isTrue(
this.updateSpy.calledOnce, 'Expected an update to be triggered');
});
test('deleting a parameter triggers an update', function() {
const procedureModel =
new Blockly.procedures.ObservableProcedureModel(
this.workspace, 'test name')
.insertParameter(
new Blockly.procedures.ObservableParameterModel(
this.workspace));
this.procedureMap.add(procedureModel);
this.updateSpy.resetHistory();
procedureModel.deleteParameter(0);
chai.assert.isTrue(
this.updateSpy.calledOnce, 'Expected an update to be triggered');
});
});
suite('parameter model updates', function() {
test('setting the name triggers an update', function() {
const parameterModel =
new Blockly.procedures.ObservableParameterModel(
this.workspace, 'test1');
this.procedureMap.add(
new Blockly.procedures.ObservableProcedureModel(
this.workspace, 'test name')
.insertParameter(parameterModel));
this.updateSpy.resetHistory();
parameterModel.setName('test2');
chai.assert.isTrue(
this.updateSpy.calledOnce, 'Expected an update to be triggered');
});
test('modifying the variable model does not trigger an update', function() {
const parameterModel =
new Blockly.procedures.ObservableParameterModel(
this.workspace, 'test1');
this.procedureMap.add(
new Blockly.procedures.ObservableProcedureModel(
this.workspace, 'test name')
.insertParameter(parameterModel));
this.updateSpy.resetHistory();
const variableModel = parameterModel.getVariableModel();
variableModel.name = 'some name';
variableModel.type = 'some type';
chai.assert.isFalse(
this.updateSpy.called, 'Expected no update to be triggered');
});
});
});
suite('backing variable to parameters', function() {
test(
'construction references an existing variable if available',
function() {
const variable = this.workspace.createVariable('test1');
const param = new Blockly.procedures.ObservableParameterModel(
this.workspace, 'test1');
chai.assert.equal(
variable,
param.getVariableModel(),
'Expected the parameter model to reference the existing variable');
});
test('construction creates a variable if none exists', function() {
const param = new Blockly.procedures.ObservableParameterModel(
this.workspace, 'test1');
chai.assert.equal(
this.workspace.getVariable('test1'),
param.getVariableModel(),
'Expected the parameter model to create a variable');
});
test('setName references an existing variable if available', function() {
const variable = this.workspace.createVariable('test2');
const param = new Blockly.procedures.ObservableParameterModel(
this.workspace, 'test1');
param.setName('test2');
chai.assert.equal(
variable,
param.getVariableModel(),
'Expected the parameter model to reference the existing variable');
});
test('setName creates a variable if none exits', function() {
const param = new Blockly.procedures.ObservableParameterModel(
this.workspace, 'test1');
param.setName('test2');
chai.assert.equal(
this.workspace.getVariable('test2'),
param.getVariableModel(),
'Expected the parameter model to create a variable');
});
test('setTypes is unimplemented', function() {
const param = new Blockly.procedures.ObservableParameterModel(
this.workspace, 'test1');
chai.assert.throws(
() => {
param.setTypes(['some', 'types']);
},
'The built-in ParameterModel does not support typing');
});
});
});

View File

@@ -148,3 +148,92 @@ export function createProcCallBlock(
`</block>`
), workspace);
}
export class MockProcedureModel {
constructor() {
this.id = Blockly.utils.idGenerator.genUid();
this.name = '';
this.parameters = [];
this.returnTypes = null;
this.enabled = true;
}
setName(name) {
this.name = name;
return this;
}
insertParameter(parameterModel, index) {
this.parameters.splice(index, 0, parameterModel);
return this;
}
deleteParameter(index) {
this.parameters.splice(index, 1);
return this;
}
setReturnTypes(types) {
this.returnTypes = types;
return this;
}
setEnabled(enabled) {
this.enabled = enabled;
return this;
}
getId() {
return this.id;
}
getName() {
return this.name;
}
getParameter(index) {
return this.parameters[index];
}
getParameters() {
return [...this.parameters];
}
getReturnTypes() {
return this.returnTypes;
}
getEnabled() {
return this.enabled;
}
}
export class MockParameterModel {
constructor(name) {
this.id = Blockly.utils.idGenerator.genUid();
this.name = name;
this.types = [];
}
setName(name) {
this.name = name;
return this;
}
setTypes(types) {
this.types = types;
return this;
}
getName() {
return this.name;
}
getTypes() {
return this.types;
}
getId() {
return this.id;
}
}