Add optionality for parameters
This commit allows for parameters that does not require any arguments to be provided in function calls. It changes collection syntax where parameters are list of objects instead of primitive strings. A parameter has now 'name' and 'optional' properties. 'name' is required and used in same way as older strings as parameter definitions. 'Optional' property is optional, 'false' is the default behavior if undefined. It also adds additional validation to restrict parameter names to alphanumeric strings to have a clear syntax in expressions.
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { FunctionCallArgument } from '@/application/Parser/Script/Compiler/FunctionCall/Argument/FunctionCallArgument';
|
||||
import { testParameterName } from '../../ParameterNameTestRunner';
|
||||
|
||||
describe('FunctionCallArgument', () => {
|
||||
describe('ctor', () => {
|
||||
describe('parameter name', () => {
|
||||
testParameterName(
|
||||
(parameterName) => new FunctionCallArgumentBuilder()
|
||||
.withParameterName(parameterName)
|
||||
.build()
|
||||
.parameterName,
|
||||
);
|
||||
});
|
||||
it('throws if argument value is undefined', () => {
|
||||
// arrange
|
||||
const parameterName = 'paramName';
|
||||
const expectedError = `undefined argument value for "${parameterName}"`;
|
||||
const argumentValue = undefined;
|
||||
// act
|
||||
const act = () => new FunctionCallArgumentBuilder()
|
||||
.withParameterName(parameterName)
|
||||
.withArgumentValue(argumentValue)
|
||||
.build();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class FunctionCallArgumentBuilder {
|
||||
private parameterName = 'default-parameter-name';
|
||||
private argumentValue = 'default-argument-value';
|
||||
public withParameterName(parameterName: string) {
|
||||
this.parameterName = parameterName;
|
||||
return this;
|
||||
}
|
||||
public withArgumentValue(argumentValue: string) {
|
||||
this.argumentValue = argumentValue;
|
||||
return this;
|
||||
}
|
||||
public build() {
|
||||
return new FunctionCallArgument(this.parameterName, this.argumentValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { FunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/FunctionCall/Argument/FunctionCallArgumentCollection';
|
||||
import { FunctionCallArgumentStub } from '@tests/unit/stubs/FunctionCallArgumentStub';
|
||||
|
||||
describe('FunctionCallArgumentCollection', () => {
|
||||
describe('addArgument', () => {
|
||||
it('throws if argument is undefined', () => {
|
||||
// arrange
|
||||
const errorMessage = 'undefined argument';
|
||||
const arg = undefined;
|
||||
const sut = new FunctionCallArgumentCollection();
|
||||
// act
|
||||
const act = () => sut.addArgument(arg);
|
||||
// assert
|
||||
expect(act).to.throw(errorMessage);
|
||||
});
|
||||
it('throws if parameter value is already provided', () => {
|
||||
// arrange
|
||||
const duplicateParameterName = 'duplicateParam';
|
||||
const errorMessage = `argument value for parameter ${duplicateParameterName} is already provided`;
|
||||
const arg1 = new FunctionCallArgumentStub().withParameterName(duplicateParameterName);
|
||||
const arg2 = new FunctionCallArgumentStub().withParameterName(duplicateParameterName);
|
||||
const sut = new FunctionCallArgumentCollection();
|
||||
// act
|
||||
sut.addArgument(arg1);
|
||||
const act = () => sut.addArgument(arg2);
|
||||
// assert
|
||||
expect(act).to.throw(errorMessage);
|
||||
});
|
||||
});
|
||||
describe('getAllParameterNames', () => {
|
||||
it('returns as expected', () => {
|
||||
// arrange
|
||||
const testCases = [ {
|
||||
name: 'no args',
|
||||
args: [],
|
||||
expected: [],
|
||||
}, {
|
||||
name: 'with some args',
|
||||
args: [
|
||||
new FunctionCallArgumentStub().withParameterName('a-param-name'),
|
||||
new FunctionCallArgumentStub().withParameterName('b-param-name')],
|
||||
expected: [ 'a-param-name', 'b-param-name'],
|
||||
}];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const sut = new FunctionCallArgumentCollection();
|
||||
// act
|
||||
for (const arg of testCase.args) {
|
||||
sut.addArgument(arg);
|
||||
}
|
||||
const actual = sut.getAllParameterNames();
|
||||
// assert
|
||||
expect(actual).to.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('getArgument', () => {
|
||||
it('throws if parameter name is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined parameter name';
|
||||
const undefinedValues = [ '', undefined ];
|
||||
for (const undefinedValue of undefinedValues) {
|
||||
const sut = new FunctionCallArgumentCollection();
|
||||
// act
|
||||
const act = () => sut.getArgument(undefinedValue);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
}
|
||||
});
|
||||
it('throws if argument does not exist', () => {
|
||||
// arrange
|
||||
const parameterName = 'nonExistingParam';
|
||||
const expectedError = `parameter does not exist: ${parameterName}`;
|
||||
const sut = new FunctionCallArgumentCollection();
|
||||
// act
|
||||
const act = () => sut.getArgument(parameterName);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('returns argument as expected', () => {
|
||||
// arrange
|
||||
const expected = new FunctionCallArgumentStub()
|
||||
.withParameterName('expectedName')
|
||||
.withArgumentValue('expectedValue');
|
||||
const sut = new FunctionCallArgumentCollection();
|
||||
// act
|
||||
sut.addArgument(expected);
|
||||
const actual = sut.getArgument(expected.parameterName);
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('hasArgument', () => {
|
||||
it('throws if parameter name is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined parameter name';
|
||||
const undefinedValues = [ '', undefined ];
|
||||
for (const undefinedValue of undefinedValues) {
|
||||
const sut = new FunctionCallArgumentCollection();
|
||||
// act
|
||||
const act = () => sut.hasArgument(undefinedValue);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
}
|
||||
});
|
||||
describe('returns as expected', () => {
|
||||
// arrange
|
||||
const testCases = [ {
|
||||
name: 'argument exists',
|
||||
parameter: 'existing-parameter-name',
|
||||
args: [
|
||||
new FunctionCallArgumentStub().withParameterName('existing-parameter-name'),
|
||||
new FunctionCallArgumentStub().withParameterName('unrelated-parameter-name'),
|
||||
],
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: 'argument does not exist',
|
||||
parameter: 'not-existing-parameter-name',
|
||||
args: [
|
||||
new FunctionCallArgumentStub().withParameterName('unrelated-parameter-name-b'),
|
||||
new FunctionCallArgumentStub().withParameterName('unrelated-parameter-name-a'),
|
||||
],
|
||||
expected: false,
|
||||
}];
|
||||
for (const testCase of testCases) {
|
||||
it(`"${testCase.name}" returns "${testCase.expected}"`, () => {
|
||||
const sut = new FunctionCallArgumentCollection();
|
||||
// act
|
||||
for (const arg of testCase.args) {
|
||||
sut.addArgument(arg);
|
||||
}
|
||||
const actual = sut.hasArgument(testCase.parameter);
|
||||
// assert
|
||||
expect(actual).to.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { FunctionCall } from '@/application/Parser/Script/Compiler/FunctionCall/FunctionCall';
|
||||
import { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/FunctionCall/Argument/IFunctionCallArgumentCollection';
|
||||
import { FunctionCallArgumentCollectionStub } from '@tests/unit/stubs/FunctionCallArgumentCollectionStub';
|
||||
|
||||
describe('FunctionCall', () => {
|
||||
describe('ctor', () => {
|
||||
it('throws when args is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined args';
|
||||
const args = undefined;
|
||||
// act
|
||||
const act = () => new FunctionCallBuilder()
|
||||
.withArgs(args)
|
||||
.build();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws when function name is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'empty function name in function call';
|
||||
const functionName = undefined;
|
||||
// act
|
||||
const act = () => new FunctionCallBuilder()
|
||||
.withFunctionName(functionName)
|
||||
.build();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class FunctionCallBuilder {
|
||||
private functionName = 'functionName';
|
||||
private args: IReadOnlyFunctionCallArgumentCollection = new FunctionCallArgumentCollectionStub();
|
||||
|
||||
public withFunctionName(functionName: string) {
|
||||
this.functionName = functionName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withArgs(args: IReadOnlyFunctionCallArgumentCollection) {
|
||||
this.args = args;
|
||||
return this;
|
||||
}
|
||||
|
||||
public build() {
|
||||
return new FunctionCall(this.functionName, this.args);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import { IExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expre
|
||||
import { ExpressionsCompilerStub } from '@tests/unit/stubs/ExpressionsCompilerStub';
|
||||
import { SharedFunctionCollectionStub } from '@tests/unit/stubs/SharedFunctionCollectionStub';
|
||||
import { SharedFunctionStub } from '@tests/unit/stubs/SharedFunctionStub';
|
||||
import { FunctionCallArgumentCollectionStub } from '@tests/unit/stubs/FunctionCallArgumentCollectionStub';
|
||||
|
||||
describe('FunctionCallCompiler', () => {
|
||||
describe('compileCall', () => {
|
||||
@@ -52,7 +53,7 @@ describe('FunctionCallCompiler', () => {
|
||||
});
|
||||
it('throws if call sequence has undefined function name', () => {
|
||||
// arrange
|
||||
const expectedError = 'empty function name called';
|
||||
const expectedError = 'empty function name in function call';
|
||||
const call: FunctionCallData[] = [
|
||||
{ function: 'function-name' },
|
||||
{ function: undefined },
|
||||
@@ -91,13 +92,14 @@ describe('FunctionCallCompiler', () => {
|
||||
it(testCase.name, () => {
|
||||
const func = new SharedFunctionStub()
|
||||
.withName('test-function-name')
|
||||
.withParameters(...testCase.functionParameters);
|
||||
.withParameterNames(...testCase.functionParameters);
|
||||
let params: FunctionCallParametersData = {};
|
||||
for (const parameter of testCase.callParameters) {
|
||||
params = {...params, [parameter]: 'defined-parameter-value '};
|
||||
}
|
||||
const call: FunctionCallData = { function: func.name, parameters: params };
|
||||
const functions = new SharedFunctionCollectionStub().withFunction(func);
|
||||
const functions = new SharedFunctionCollectionStub()
|
||||
.withFunction(func);
|
||||
const sut = new MockableFunctionCallCompiler();
|
||||
// act
|
||||
const act = () => sut.compileCall(call, functions);
|
||||
@@ -134,38 +136,33 @@ describe('FunctionCallCompiler', () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
describe('builds code as expected', () => {
|
||||
describe('builds single call as expected', () => {
|
||||
// arrange
|
||||
const parametersTestCases = [
|
||||
{
|
||||
name: 'undefined parameters',
|
||||
parameters: undefined,
|
||||
parameterValues: undefined,
|
||||
},
|
||||
{
|
||||
name: 'empty parameters',
|
||||
parameters: [],
|
||||
parameterValues: { },
|
||||
callArgs: { },
|
||||
},
|
||||
{
|
||||
name: 'non-empty parameters',
|
||||
parameters: [ 'param1', 'param2' ],
|
||||
parameterValues: { param1: 'value1', param2: 'value2' },
|
||||
callArgs: { param1: 'value1', param2: 'value2' },
|
||||
},
|
||||
];
|
||||
for (const testCase of parametersTestCases) {
|
||||
it(testCase.name, () => {
|
||||
const expectedExecute = `expected-execute`;
|
||||
const expectedRevert = `expected-revert`;
|
||||
const func = new SharedFunctionStub().withParameters(...testCase.parameters);
|
||||
const func = new SharedFunctionStub().withParameterNames(...testCase.parameters);
|
||||
const functions = new SharedFunctionCollectionStub().withFunction(func);
|
||||
const call: FunctionCallData = { function: func.name, parameters: testCase.parameterValues };
|
||||
const call: FunctionCallData = { function: func.name, parameters: testCase.callArgs };
|
||||
const args = new FunctionCallArgumentCollectionStub().withArguments(testCase.callArgs);
|
||||
const expressionsCompilerMock = new ExpressionsCompilerStub()
|
||||
.setup(func.code, testCase.parameterValues, expectedExecute)
|
||||
.setup(func.revertCode, testCase.parameterValues, expectedRevert);
|
||||
.setup(func.code, args, expectedExecute)
|
||||
.setup(func.revertCode, args, expectedRevert);
|
||||
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
|
||||
// act
|
||||
const actual = sut.compileCall(call, functions);
|
||||
@@ -183,7 +180,7 @@ describe('FunctionCallCompiler', () => {
|
||||
.withRevertCode('first-function-revert-code');
|
||||
const secondFunction = new SharedFunctionStub()
|
||||
.withName('second-function-name')
|
||||
.withParameters('testParameter')
|
||||
.withParameterNames('testParameter')
|
||||
.withCode('second-function-code')
|
||||
.withRevertCode('second-function-revert-code');
|
||||
const secondCallArguments = { testParameter: 'testValue' };
|
||||
@@ -191,11 +188,14 @@ describe('FunctionCallCompiler', () => {
|
||||
{ function: firstFunction.name },
|
||||
{ function: secondFunction.name, parameters: secondCallArguments },
|
||||
];
|
||||
const firstFunctionCallArgs = new FunctionCallArgumentCollectionStub();
|
||||
const secondFunctionCallArgs = new FunctionCallArgumentCollectionStub()
|
||||
.withArguments(secondCallArguments);
|
||||
const expressionsCompilerMock = new ExpressionsCompilerStub()
|
||||
.setup(firstFunction.code, {}, firstFunction.code)
|
||||
.setup(firstFunction.revertCode, {}, firstFunction.revertCode)
|
||||
.setup(secondFunction.code, secondCallArguments, secondFunction.code)
|
||||
.setup(secondFunction.revertCode, secondCallArguments, secondFunction.revertCode);
|
||||
.setup(firstFunction.code, firstFunctionCallArgs, firstFunction.code)
|
||||
.setup(firstFunction.revertCode, firstFunctionCallArgs, firstFunction.revertCode)
|
||||
.setup(secondFunction.code, secondFunctionCallArgs, secondFunction.code)
|
||||
.setup(secondFunction.revertCode, secondFunctionCallArgs, secondFunction.revertCode);
|
||||
const expectedExecute = `${firstFunction.code}\n${secondFunction.code}`;
|
||||
const expectedRevert = `${firstFunction.revertCode}\n${secondFunction.revertCode}`;
|
||||
const functions = new SharedFunctionCollectionStub()
|
||||
|
||||
Reference in New Issue
Block a user