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:
@@ -3,7 +3,11 @@ import { expect } from 'chai';
|
||||
import { ExpressionPosition } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionPosition';
|
||||
import { ExpressionEvaluator } from '@/application/Parser/Script/Compiler/Expressions/Expression/Expression';
|
||||
import { Expression } from '@/application/Parser/Script/Compiler/Expressions/Expression/Expression';
|
||||
import { ExpressionArguments } from '@/application/Parser/Script/Compiler/Expressions/Expression/IExpression';
|
||||
import { IReadOnlyFunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/IFunctionParameterCollection';
|
||||
import { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/FunctionCall/Argument/IFunctionCallArgumentCollection';
|
||||
import { FunctionCallArgumentCollectionStub } from '@tests/unit/stubs/FunctionCallArgumentCollectionStub';
|
||||
import { FunctionParameterCollectionStub } from '@tests/unit/stubs/FunctionParameterCollectionStub';
|
||||
import { FunctionCallArgumentStub } from '@tests/unit/stubs/FunctionCallArgumentStub';
|
||||
|
||||
describe('Expression', () => {
|
||||
describe('ctor', () => {
|
||||
@@ -39,11 +43,15 @@ describe('Expression', () => {
|
||||
.withParameters(parameters)
|
||||
.build();
|
||||
// assert
|
||||
expect(actual.parameters).to.have.lengthOf(0);
|
||||
expect(actual.parameters);
|
||||
expect(actual.parameters.all);
|
||||
expect(actual.parameters.all.length).to.equal(0);
|
||||
});
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
const expected = [ 'firstParameterName', 'secondParameterName' ];
|
||||
const expected = new FunctionParameterCollectionStub()
|
||||
.withParameterName('firstParameterName')
|
||||
.withParameterName('secondParameterName');
|
||||
// act
|
||||
const actual = new ExpressionBuilder()
|
||||
.withParameters(expected)
|
||||
@@ -67,52 +75,119 @@ describe('Expression', () => {
|
||||
});
|
||||
});
|
||||
describe('evaluate', () => {
|
||||
describe('throws with invalid arguments', () => {
|
||||
const testCases = [
|
||||
{
|
||||
name: 'throws if arguments is undefined',
|
||||
args: undefined,
|
||||
expectedError: 'undefined args, send empty collection instead',
|
||||
},
|
||||
{
|
||||
name: 'throws when some of the required args are not provided',
|
||||
sut: (i: ExpressionBuilder) => i.withParameterNames(['a', 'b', 'c'], false),
|
||||
args: new FunctionCallArgumentCollectionStub().withArgument('b', 'provided'),
|
||||
expectedError: 'argument values are provided for required parameters: "a", "c"',
|
||||
},
|
||||
{
|
||||
name: 'throws when none of the required args are not provided',
|
||||
sut: (i: ExpressionBuilder) => i.withParameterNames(['a', 'b'], false),
|
||||
args: new FunctionCallArgumentCollectionStub().withArgument('c', 'unrelated'),
|
||||
expectedError: 'argument values are provided for required parameters: "a", "b"',
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// arrange
|
||||
const sutBuilder = new ExpressionBuilder();
|
||||
if (testCase.sut) {
|
||||
testCase.sut(sutBuilder);
|
||||
}
|
||||
const sut = sutBuilder.build();
|
||||
// act
|
||||
const act = () => sut.evaluate(testCase.args);
|
||||
// assert
|
||||
expect(act).to.throw(testCase.expectedError);
|
||||
});
|
||||
}
|
||||
});
|
||||
it('returns result from evaluator', () => {
|
||||
// arrange
|
||||
const evaluatorMock: ExpressionEvaluator = (args) => JSON.stringify(args);
|
||||
const givenArguments = { parameter1: 'value1', parameter2: 'value2' };
|
||||
const evaluatorMock: ExpressionEvaluator = (args) =>
|
||||
`"${args
|
||||
.getAllParameterNames()
|
||||
.map((name) => args.getArgument(name))
|
||||
.map((arg) => `${arg.parameterName}': '${arg.argumentValue}'`)
|
||||
.join('", "')}"`;
|
||||
const givenArguments = new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('parameter1', 'value1')
|
||||
.withArgument('parameter2', 'value2');
|
||||
const expectedParameterNames = givenArguments.getAllParameterNames();
|
||||
const expected = evaluatorMock(givenArguments);
|
||||
const sut = new ExpressionBuilder()
|
||||
.withEvaluator(evaluatorMock)
|
||||
.withParameters(Object.keys(givenArguments))
|
||||
.withParameterNames(expectedParameterNames)
|
||||
.build();
|
||||
// arrange
|
||||
const actual = sut.evaluate(givenArguments);
|
||||
// assert
|
||||
expect(expected).to.equal(actual);
|
||||
expect(expected).to.equal(actual,
|
||||
`\nGiven arguments: ${JSON.stringify(givenArguments)}\n` +
|
||||
`\nExpected parameter names: ${JSON.stringify(expectedParameterNames)}\n`,
|
||||
);
|
||||
});
|
||||
it('filters unused arguments', () => {
|
||||
describe('filters unused parameters', () => {
|
||||
// arrange
|
||||
let actual: ExpressionArguments = {};
|
||||
const evaluatorMock: ExpressionEvaluator = (providedArgs) => {
|
||||
Object.keys(providedArgs)
|
||||
.forEach((name) => actual = {...actual, [name]: providedArgs[name] });
|
||||
return '';
|
||||
};
|
||||
const parameterNameToHave = 'parameterToHave';
|
||||
const parameterNameToIgnore = 'parameterToIgnore';
|
||||
const sut = new ExpressionBuilder()
|
||||
.withEvaluator(evaluatorMock)
|
||||
.withParameters([ parameterNameToHave ])
|
||||
.build();
|
||||
const args: ExpressionArguments = {
|
||||
[parameterNameToHave]: 'value-to-have',
|
||||
[parameterNameToIgnore]: 'value-to-ignore',
|
||||
};
|
||||
const expected: ExpressionArguments = {
|
||||
[parameterNameToHave]: args[parameterNameToHave],
|
||||
};
|
||||
// arrange
|
||||
sut.evaluate(args);
|
||||
// assert
|
||||
expect(expected).to.deep.equal(actual);
|
||||
const testCases = [
|
||||
{
|
||||
name: 'with a provided argument',
|
||||
expressionParameters: new FunctionParameterCollectionStub()
|
||||
.withParameterName('parameterToHave', false),
|
||||
arguments: new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('parameterToHave', 'value-to-have')
|
||||
.withArgument('parameterToIgnore', 'value-to-ignore'),
|
||||
expectedArguments: [
|
||||
new FunctionCallArgumentStub()
|
||||
.withParameterName('parameterToHave').withArgumentValue('value-to-have'),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'without a provided argument',
|
||||
expressionParameters: new FunctionParameterCollectionStub()
|
||||
.withParameterName('parameterToHave', false)
|
||||
.withParameterName('parameterToIgnore', true),
|
||||
arguments: new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('parameterToHave', 'value-to-have'),
|
||||
expectedArguments: [
|
||||
new FunctionCallArgumentStub()
|
||||
.withParameterName('parameterToHave').withArgumentValue('value-to-have'),
|
||||
],
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
let actual: IReadOnlyFunctionCallArgumentCollection;
|
||||
const evaluatorMock: ExpressionEvaluator = (providedArgs) => {
|
||||
actual = providedArgs;
|
||||
return '';
|
||||
};
|
||||
const sut = new ExpressionBuilder()
|
||||
.withEvaluator(evaluatorMock)
|
||||
.withParameters(testCase.expressionParameters)
|
||||
.build();
|
||||
// act
|
||||
sut.evaluate(testCase.arguments);
|
||||
// assert
|
||||
const actualArguments = actual.getAllParameterNames().map((name) => actual.getArgument(name));
|
||||
expect(actualArguments).to.deep.equal(testCase.expectedArguments);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class ExpressionBuilder {
|
||||
private position: ExpressionPosition = new ExpressionPosition(0, 5);
|
||||
private parameters: readonly string[] = new Array<string>();
|
||||
private parameters: IReadOnlyFunctionParameterCollection = new FunctionParameterCollectionStub();
|
||||
|
||||
public withPosition(position: ExpressionPosition) {
|
||||
this.position = position;
|
||||
@@ -122,10 +197,20 @@ class ExpressionBuilder {
|
||||
this.evaluator = evaluator;
|
||||
return this;
|
||||
}
|
||||
public withParameters(parameters: string[]) {
|
||||
public withParameters(parameters: IReadOnlyFunctionParameterCollection) {
|
||||
this.parameters = parameters;
|
||||
return this;
|
||||
}
|
||||
public withParameterName(parameterName: string, isOptional: boolean = true) {
|
||||
const collection = new FunctionParameterCollectionStub()
|
||||
.withParameterName(parameterName, isOptional);
|
||||
return this.withParameters(collection);
|
||||
}
|
||||
public withParameterNames(parameterNames: string[], isOptional: boolean = true) {
|
||||
const collection = new FunctionParameterCollectionStub()
|
||||
.withParameterNames(parameterNames, isOptional);
|
||||
return this.withParameters(collection);
|
||||
}
|
||||
public build() {
|
||||
return new Expression(this.position, this.evaluator, this.parameters);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expres
|
||||
import { IExpressionParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/IExpressionParser';
|
||||
import { ExpressionStub } from '@tests/unit/stubs/ExpressionStub';
|
||||
import { ExpressionParserStub } from '@tests/unit/stubs/ExpressionParserStub';
|
||||
import { FunctionCallArgumentCollectionStub } from '@tests/unit/stubs/FunctionCallArgumentCollectionStub';
|
||||
|
||||
describe('ExpressionsCompiler', () => {
|
||||
describe('compileExpressions', () => {
|
||||
@@ -22,8 +23,18 @@ describe('ExpressionsCompiler', () => {
|
||||
{
|
||||
name: 'unordered expressions',
|
||||
expressions: [
|
||||
new ExpressionStub().withPosition(6, 13).withEvaluatedResult('a'),
|
||||
new ExpressionStub().withPosition(20, 27).withEvaluatedResult('b'),
|
||||
new ExpressionStub().withPosition(6, 13).withEvaluatedResult('a'),
|
||||
],
|
||||
expected: 'part1 a part2 b part3',
|
||||
},
|
||||
{
|
||||
name: 'with an optional expected argument that is not provided',
|
||||
expressions: [
|
||||
new ExpressionStub().withPosition(6, 13).withEvaluatedResult('a')
|
||||
.withParameterNames(['optionalParameter'], true),
|
||||
new ExpressionStub().withPosition(20, 27).withEvaluatedResult('b')
|
||||
.withParameterNames(['optionalParameterTwo'], true),
|
||||
],
|
||||
expected: 'part1 a part2 b part3',
|
||||
},
|
||||
@@ -37,97 +48,89 @@ describe('ExpressionsCompiler', () => {
|
||||
it(testCase.name, () => {
|
||||
const expressionParserMock = new ExpressionParserStub()
|
||||
.withResult(testCase.expressions);
|
||||
const args = new FunctionCallArgumentCollectionStub();
|
||||
const sut = new MockableExpressionsCompiler(expressionParserMock);
|
||||
// act
|
||||
const actual = sut.compileExpressions(code);
|
||||
const actual = sut.compileExpressions(code, args);
|
||||
// assert
|
||||
expect(actual).to.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
it('passes arguments to expressions as expected', () => {
|
||||
// arrange
|
||||
const expected = {
|
||||
parameter1: 'value1',
|
||||
parameter2: 'value2',
|
||||
};
|
||||
const code = 'non-important';
|
||||
const expressions = [
|
||||
new ExpressionStub(),
|
||||
new ExpressionStub(),
|
||||
];
|
||||
const expressionParserMock = new ExpressionParserStub()
|
||||
.withResult(expressions);
|
||||
const sut = new MockableExpressionsCompiler(expressionParserMock);
|
||||
// act
|
||||
sut.compileExpressions(code, expected);
|
||||
// assert
|
||||
expect(expressions[0].callHistory).to.have.lengthOf(1);
|
||||
expect(expressions[0].callHistory[0]).to.equal(expected);
|
||||
expect(expressions[1].callHistory).to.have.lengthOf(1);
|
||||
expect(expressions[1].callHistory[0]).to.equal(expected);
|
||||
describe('arguments', () => {
|
||||
it('passes arguments to expressions as expected', () => {
|
||||
// arrange
|
||||
const expected = new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('test-arg', 'test-value');
|
||||
const code = 'non-important';
|
||||
const expressions = [
|
||||
new ExpressionStub(),
|
||||
new ExpressionStub(),
|
||||
];
|
||||
const expressionParserMock = new ExpressionParserStub()
|
||||
.withResult(expressions);
|
||||
const sut = new MockableExpressionsCompiler(expressionParserMock);
|
||||
// act
|
||||
sut.compileExpressions(code, expected);
|
||||
// assert
|
||||
expect(expressions[0].callHistory).to.have.lengthOf(1);
|
||||
expect(expressions[0].callHistory[0]).to.equal(expected);
|
||||
expect(expressions[1].callHistory).to.have.lengthOf(1);
|
||||
expect(expressions[1].callHistory[0]).to.equal(expected);
|
||||
});
|
||||
it('throws if arguments is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined args, send empty collection instead';
|
||||
const args = undefined;
|
||||
const expressionParserMock = new ExpressionParserStub();
|
||||
const sut = new MockableExpressionsCompiler(expressionParserMock);
|
||||
// act
|
||||
const act = () => sut.compileExpressions('code', args);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('throws when expected argument is not provided', () => {
|
||||
describe('throws when expected argument is not provided but used in code', () => {
|
||||
// arrange
|
||||
const noParameterTestCases = [
|
||||
const testCases = [
|
||||
{
|
||||
name: 'empty parameters',
|
||||
expressions: [
|
||||
new ExpressionStub().withParameters('parameter'),
|
||||
new ExpressionStub().withParameterNames(['parameter'], false),
|
||||
],
|
||||
args: {},
|
||||
expectedError: 'parameter value(s) not provided for: "parameter"',
|
||||
args: new FunctionCallArgumentCollectionStub(),
|
||||
expectedError: 'parameter value(s) not provided for: "parameter" but used in code',
|
||||
},
|
||||
{
|
||||
name: 'undefined parameters',
|
||||
name: 'unnecessary parameter is provided',
|
||||
expressions: [
|
||||
new ExpressionStub().withParameters('parameter'),
|
||||
new ExpressionStub().withParameterNames(['parameter'], false),
|
||||
],
|
||||
args: undefined,
|
||||
expectedError: 'parameter value(s) not provided for: "parameter"',
|
||||
},
|
||||
{
|
||||
name: 'unnecessary parameter provided',
|
||||
expressions: [
|
||||
new ExpressionStub().withParameters('parameter'),
|
||||
],
|
||||
args: {
|
||||
unnecessaryParameter: 'unnecessaryValue',
|
||||
},
|
||||
expectedError: 'parameter value(s) not provided for: "parameter"',
|
||||
},
|
||||
{
|
||||
name: 'undefined value',
|
||||
expressions: [
|
||||
new ExpressionStub().withParameters('parameter'),
|
||||
],
|
||||
args: {
|
||||
parameter: undefined,
|
||||
},
|
||||
expectedError: 'parameter value(s) not provided for: "parameter"',
|
||||
args: new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('unnecessaryParameter', 'unnecessaryValue'),
|
||||
expectedError: 'parameter value(s) not provided for: "parameter" but used in code',
|
||||
},
|
||||
{
|
||||
name: 'multiple values are not provided',
|
||||
expressions: [
|
||||
new ExpressionStub().withParameters('parameter1'),
|
||||
new ExpressionStub().withParameters('parameter2', 'parameter3'),
|
||||
new ExpressionStub().withParameterNames(['parameter1'], false),
|
||||
new ExpressionStub().withParameterNames(['parameter2', 'parameter3'], false),
|
||||
],
|
||||
args: {},
|
||||
expectedError: 'parameter value(s) not provided for: "parameter1", "parameter2", "parameter3"',
|
||||
args: new FunctionCallArgumentCollectionStub(),
|
||||
expectedError: 'parameter value(s) not provided for: "parameter1", "parameter2", "parameter3" but used in code',
|
||||
},
|
||||
{
|
||||
name: 'some values are provided',
|
||||
expressions: [
|
||||
new ExpressionStub().withParameters('parameter1'),
|
||||
new ExpressionStub().withParameters('parameter2', 'parameter3'),
|
||||
new ExpressionStub().withParameterNames(['parameter1'], false),
|
||||
new ExpressionStub().withParameterNames(['parameter2', 'parameter3'], false),
|
||||
],
|
||||
args: {
|
||||
parameter2: 'value',
|
||||
},
|
||||
expectedError: 'parameter value(s) not provided for: "parameter1", "parameter3"',
|
||||
args: new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('parameter2', 'value'),
|
||||
expectedError: 'parameter value(s) not provided for: "parameter1", "parameter3" but used in code',
|
||||
},
|
||||
];
|
||||
for (const testCase of noParameterTestCases) {
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const code = 'non-important-code';
|
||||
const expressionParserMock = new ExpressionParserStub()
|
||||
@@ -145,8 +148,9 @@ describe('ExpressionsCompiler', () => {
|
||||
const expected = 'expected-code';
|
||||
const expressionParserMock = new ExpressionParserStub();
|
||||
const sut = new MockableExpressionsCompiler(expressionParserMock);
|
||||
const args = new FunctionCallArgumentCollectionStub();
|
||||
// act
|
||||
sut.compileExpressions(expected);
|
||||
sut.compileExpressions(expected, args);
|
||||
// assert
|
||||
expect(expressionParserMock.callHistory).to.have.lengthOf(1);
|
||||
expect(expressionParserMock.callHistory[0]).to.equal(expected);
|
||||
|
||||
@@ -3,6 +3,7 @@ import { expect } from 'chai';
|
||||
import { ExpressionEvaluator } from '@/application/Parser/Script/Compiler/Expressions/Expression/Expression';
|
||||
import { IPrimitiveExpression, RegexParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/RegexParser';
|
||||
import { ExpressionPosition } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionPosition';
|
||||
import { FunctionParameterStub } from '@tests/unit/stubs/FunctionParameterStub';
|
||||
|
||||
describe('RegexParser', () => {
|
||||
describe('findExpressions', () => {
|
||||
@@ -59,7 +60,10 @@ describe('RegexParser', () => {
|
||||
});
|
||||
it('sets parameters as expected', () => {
|
||||
// arrange
|
||||
const expected = [ 'parameter1', 'parameter2' ];
|
||||
const expected = [
|
||||
new FunctionParameterStub().withName('parameter1').withOptionality(true),
|
||||
new FunctionParameterStub().withName('parameter2').withOptionality(false),
|
||||
];
|
||||
const regex = /hello/g;
|
||||
const code = 'hello';
|
||||
const builder = (): IPrimitiveExpression => ({
|
||||
@@ -71,7 +75,7 @@ describe('RegexParser', () => {
|
||||
const expressions = sut.findExpressions(code);
|
||||
// assert
|
||||
expect(expressions).to.have.lengthOf(1);
|
||||
expect(expressions[0].parameters).to.equal(expected);
|
||||
expect(expressions[0].parameters.all).to.deep.equal(expected);
|
||||
});
|
||||
it('sets expected position', () => {
|
||||
// arrange
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { ParameterSubstitutionParser } from '@/application/Parser/Script/Compiler/Expressions/SyntaxParsers/ParameterSubstitutionParser';
|
||||
import { ExpressionPosition } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionPosition';
|
||||
import { ExpressionArguments } from '@/application/Parser/Script/Compiler/Expressions/Expression/IExpression';
|
||||
import { FunctionCallArgumentCollectionStub } from '@tests/unit/stubs/FunctionCallArgumentCollectionStub';
|
||||
|
||||
describe('ParameterSubstitutionParser', () => {
|
||||
describe('finds at expected positions', () => {
|
||||
@@ -44,36 +44,25 @@ describe('ParameterSubstitutionParser', () => {
|
||||
const testCases = [ {
|
||||
name: 'single parameter',
|
||||
code: '{{ $parameter }}',
|
||||
args: [ {
|
||||
name: 'parameter',
|
||||
value: 'Hello world',
|
||||
}],
|
||||
args: new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('parameter', 'Hello world'),
|
||||
expected: [ 'Hello world' ],
|
||||
},
|
||||
{
|
||||
name: 'different parameters',
|
||||
code: '{{ $firstParameter }} {{ $secondParameter }}!',
|
||||
args: [ {
|
||||
name: 'firstParameter',
|
||||
value: 'Hello',
|
||||
},
|
||||
{
|
||||
name: 'secondParameter',
|
||||
value: 'World',
|
||||
}],
|
||||
args: new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('firstParameter', 'Hello')
|
||||
.withArgument('secondParameter', 'World'),
|
||||
expected: [ 'Hello', 'World' ],
|
||||
}];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const sut = new ParameterSubstitutionParser();
|
||||
let args: ExpressionArguments = {};
|
||||
for (const arg of testCase.args) {
|
||||
args = {...args, [arg.name]: arg.value };
|
||||
}
|
||||
// act
|
||||
const expressions = sut.findExpressions(testCase.code);
|
||||
// assert
|
||||
const actual = expressions.map((e) => e.evaluate(args));
|
||||
const actual = expressions.map((e) => e.evaluate(testCase.args));
|
||||
expect(actual).to.deep.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
||||
import { FunctionData } from 'js-yaml-loader!@/*';
|
||||
import { FunctionData, ParameterDefinitionData } from 'js-yaml-loader!@/*';
|
||||
import { IFunctionCallCompiler } from '@/application/Parser/Script/Compiler/FunctionCall/IFunctionCallCompiler';
|
||||
import { FunctionCompiler } from '@/application/Parser/Script/Compiler/Function/FunctionCompiler';
|
||||
import { IReadOnlyFunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/IFunctionParameterCollection';
|
||||
import { FunctionCallCompilerStub } from '@tests/unit/stubs/FunctionCallCompilerStub';
|
||||
import { FunctionDataStub } from '@tests/unit/stubs/FunctionDataStub';
|
||||
import { ParameterDefinitionDataStub } from '@tests/unit/stubs/ParameterDefinitionDataStub';
|
||||
import { FunctionParameter } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameter';
|
||||
|
||||
describe('FunctionsCompiler', () => {
|
||||
describe('compileFunctions', () => {
|
||||
@@ -34,29 +37,31 @@ describe('FunctionsCompiler', () => {
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws when function parameters have same names', () => {
|
||||
// arrange
|
||||
const parameterName = 'duplicate-parameter';
|
||||
const func = FunctionDataStub.createWithCall()
|
||||
.withParameters(parameterName, parameterName);
|
||||
const expectedError = `"${func.name}": duplicate parameter name: "${parameterName}"`;
|
||||
const sut = new MockableFunctionCompiler();
|
||||
// act
|
||||
const act = () => sut.compileFunctions([ func ]);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws when parameters is not an array of strings', () => {
|
||||
// arrange
|
||||
const parameterNameWithUnexpectedType = 5;
|
||||
const func = FunctionDataStub.createWithCall()
|
||||
.withParameters(parameterNameWithUnexpectedType as any);
|
||||
const expectedError = `unexpected parameter name type in "${func.name}"`;
|
||||
const sut = new MockableFunctionCompiler();
|
||||
// act
|
||||
const act = () => sut.compileFunctions([ func ]);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
describe('throws when parameters type is not as expected', () => {
|
||||
const testCases = [
|
||||
{
|
||||
state: 'when not an array',
|
||||
invalidType: 5,
|
||||
},
|
||||
{
|
||||
state: 'when array but not of objects',
|
||||
invalidType: [ 'a', { a: 'b'} ],
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.state, () => {
|
||||
// arrange
|
||||
const func = FunctionDataStub
|
||||
.createWithCall()
|
||||
.withParametersObject(testCase.invalidType as any);
|
||||
const expectedError = `parameters must be an array of objects in function(s) "${func.name}"`;
|
||||
const sut = new MockableFunctionCompiler();
|
||||
// act
|
||||
const act = () => sut.compileFunctions([ func ]);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('throws when when function have duplicate code', () => {
|
||||
it('code', () => {
|
||||
@@ -116,6 +121,37 @@ describe('FunctionsCompiler', () => {
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('rethrows including function name when FunctionParameter throws', () => {
|
||||
// arrange
|
||||
const functionName = 'invalid-function';
|
||||
const expectedError = `neither "code" or "call" is defined in "${functionName}"`;
|
||||
const invalidFunction = FunctionDataStub.createWithoutCallOrCodes()
|
||||
.withName(functionName);
|
||||
const sut = new MockableFunctionCompiler();
|
||||
// act
|
||||
const act = () => sut.compileFunctions([ invalidFunction ]);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('rethrows including function name when FunctionParameter throws', () => {
|
||||
// arrange
|
||||
const invalidParameterName = 'invalid function p@r4meter name';
|
||||
const functionName = 'functionName';
|
||||
let parameterException: Error;
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
try { new FunctionParameter(invalidParameterName, false); } catch (e) { parameterException = e; }
|
||||
const expectedError = `"${functionName}": ${parameterException.message}`;
|
||||
const functionData = FunctionDataStub.createWithCode()
|
||||
.withName(functionName)
|
||||
.withParameters(new ParameterDefinitionDataStub().withName(invalidParameterName));
|
||||
|
||||
// act
|
||||
const sut = new MockableFunctionCompiler();
|
||||
const act = () => sut.compileFunctions([ functionData ]);
|
||||
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
it('returns empty with empty functions', () => {
|
||||
// arrange
|
||||
@@ -136,7 +172,10 @@ describe('FunctionsCompiler', () => {
|
||||
.withName(name)
|
||||
.withCode('expected-code')
|
||||
.withRevertCode('expected-revert-code')
|
||||
.withParameters('expected-parameter-1', 'expected-parameter-2');
|
||||
.withParameters(
|
||||
new ParameterDefinitionDataStub().withName('expectedParameter').withOptionality(true),
|
||||
new ParameterDefinitionDataStub().withName('expectedParameter2').withOptionality(false),
|
||||
);
|
||||
const sut = new MockableFunctionCompiler();
|
||||
// act
|
||||
const collection = sut.compileFunctions([ expected ]);
|
||||
@@ -188,7 +227,7 @@ describe('FunctionsCompiler', () => {
|
||||
|
||||
function expectEqualFunctions(expected: FunctionData, actual: ISharedFunction) {
|
||||
expect(actual.name).to.equal(expected.name);
|
||||
expect(actual.parameters).to.deep.equal(expected.parameters);
|
||||
expect(areScrambledEqual(actual.parameters, expected.parameters));
|
||||
expectEqualFunctionCode(expected, actual);
|
||||
}
|
||||
|
||||
@@ -197,6 +236,23 @@ function expectEqualFunctionCode(expected: FunctionData, actual: ISharedFunction
|
||||
expect(actual.revertCode).to.equal(expected.revertCode);
|
||||
}
|
||||
|
||||
function areScrambledEqual(
|
||||
expected: IReadOnlyFunctionParameterCollection,
|
||||
actual: readonly ParameterDefinitionData[],
|
||||
) {
|
||||
if (expected.all.length !== actual.length) {
|
||||
return false;
|
||||
}
|
||||
for (const expectedParameter of expected.all) {
|
||||
if (!actual.some(
|
||||
(a) => a.name === expectedParameter.name
|
||||
&& (a.optional || false) === expectedParameter.isOptional)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
class MockableFunctionCompiler extends FunctionCompiler {
|
||||
constructor(functionCallCompiler: IFunctionCallCompiler = new FunctionCallCompilerStub()) {
|
||||
super(functionCallCompiler);
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { FunctionParameter } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameter';
|
||||
import { testParameterName } from '../../ParameterNameTestRunner';
|
||||
|
||||
describe('FunctionParameter', () => {
|
||||
describe('name', () => {
|
||||
testParameterName(
|
||||
(parameterName) => new FunctionParameterBuilder()
|
||||
.withName(parameterName)
|
||||
.build()
|
||||
.name,
|
||||
);
|
||||
});
|
||||
describe('isOptional', () => {
|
||||
describe('sets as expected', () => {
|
||||
// arrange
|
||||
const expectedValues = [ true, false];
|
||||
for (const expected of expectedValues) {
|
||||
it(expected.toString(), () => {
|
||||
// act
|
||||
const sut = new FunctionParameterBuilder()
|
||||
.withIsOptional(expected)
|
||||
.build();
|
||||
// expect
|
||||
expect(sut.isOptional).to.equal(expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class FunctionParameterBuilder {
|
||||
private name = 'parameterFromParameterBuilder';
|
||||
private isOptional = false;
|
||||
public withName(name: string) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
public withIsOptional(isOptional: boolean) {
|
||||
this.isOptional = isOptional;
|
||||
return this;
|
||||
}
|
||||
public build() {
|
||||
return new FunctionParameter(this.name, this.isOptional);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { FunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollection';
|
||||
import { FunctionParameterStub } from '@tests/unit/stubs/FunctionParameterStub';
|
||||
|
||||
describe('FunctionParameterCollection', () => {
|
||||
it('all returns added parameters as expected', () => {
|
||||
// arrange
|
||||
const expected = [
|
||||
new FunctionParameterStub().withName('1'),
|
||||
new FunctionParameterStub().withName('2').withOptionality(true),
|
||||
new FunctionParameterStub().withName('3').withOptionality(false),
|
||||
];
|
||||
const sut = new FunctionParameterCollection();
|
||||
for (const parameter of expected) {
|
||||
sut.addParameter(parameter);
|
||||
}
|
||||
// act
|
||||
const actual = sut.all;
|
||||
// assert
|
||||
expect(expected).to.deep.equal(actual);
|
||||
});
|
||||
it('throws when function parameters have same names', () => {
|
||||
// arrange
|
||||
const parameterName = 'duplicate-parameter';
|
||||
const expectedError = `duplicate parameter name: "${parameterName}"`;
|
||||
const sut = new FunctionParameterCollection();
|
||||
sut.addParameter(new FunctionParameterStub().withName(parameterName));
|
||||
// act
|
||||
const act = () =>
|
||||
sut.addParameter(new FunctionParameterStub().withName(parameterName));
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('addParameter', () => {
|
||||
it('throws if parameter is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined parameter';
|
||||
const value = undefined;
|
||||
const sut = new FunctionParameterCollection();
|
||||
// act
|
||||
const act = () => sut.addParameter(value);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { SharedFunction } from '@/application/Parser/Script/Compiler/Function/SharedFunction';
|
||||
import { IReadOnlyFunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/IFunctionParameterCollection';
|
||||
import { FunctionParameterCollectionStub } from '@tests/unit/stubs/FunctionParameterCollectionStub';
|
||||
|
||||
describe('SharedFunction', () => {
|
||||
describe('name', () => {
|
||||
@@ -31,25 +33,25 @@ describe('SharedFunction', () => {
|
||||
describe('parameters', () => {
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
const expected = [ 'expected-parameter' ];
|
||||
const expected = new FunctionParameterCollectionStub()
|
||||
.withParameterName('test-parameter');
|
||||
// act
|
||||
const sut = new SharedFunctionBuilder()
|
||||
.withParameters(expected)
|
||||
.build();
|
||||
// assert
|
||||
expect(sut.parameters).to.deep.equal(expected);
|
||||
expect(sut.parameters).to.equal(expected);
|
||||
});
|
||||
it('returns empty array if undefined', () => {
|
||||
it('throws if undefined', () => {
|
||||
// arrange
|
||||
const expected = [ ];
|
||||
const value = undefined;
|
||||
const expectedError = 'undefined parameters';
|
||||
const parameters = undefined;
|
||||
// act
|
||||
const sut = new SharedFunctionBuilder()
|
||||
.withParameters(value)
|
||||
const act = () => new SharedFunctionBuilder()
|
||||
.withParameters(parameters)
|
||||
.build();
|
||||
// assert
|
||||
expect(sut.parameters).to.not.equal(undefined);
|
||||
expect(sut.parameters).to.deep.equal(expected);
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('code', () => {
|
||||
@@ -97,7 +99,7 @@ describe('SharedFunction', () => {
|
||||
|
||||
class SharedFunctionBuilder {
|
||||
private name = 'name';
|
||||
private parameters: readonly string[] = [ 'parameter' ];
|
||||
private parameters: IReadOnlyFunctionParameterCollection = new FunctionParameterCollectionStub();
|
||||
private code = 'code';
|
||||
private revertCode = 'revert-code';
|
||||
|
||||
@@ -113,7 +115,7 @@ class SharedFunctionBuilder {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
public withParameters(parameters: readonly string[]) {
|
||||
public withParameters(parameters: IReadOnlyFunctionParameterCollection) {
|
||||
this.parameters = parameters;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
|
||||
export function testParameterName(action: (parameterName: string) => string) {
|
||||
describe('name', () => {
|
||||
describe('sets as expected', () => {
|
||||
// arrange
|
||||
const expectedValues = [
|
||||
'lowercase',
|
||||
'onlyLetters',
|
||||
'l3tt3rsW1thNumb3rs',
|
||||
];
|
||||
for (const expected of expectedValues) {
|
||||
it(expected, () => {
|
||||
// act
|
||||
const value = action(expected);
|
||||
// assert
|
||||
expect(value).to.equal(expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('throws if invalid', () => {
|
||||
// arrange
|
||||
const testCases = [
|
||||
{
|
||||
name: 'undefined',
|
||||
value: undefined,
|
||||
expectedError: 'undefined parameter name',
|
||||
},
|
||||
{
|
||||
name: 'empty',
|
||||
value: '',
|
||||
expectedError: 'undefined parameter name',
|
||||
},
|
||||
{
|
||||
name: 'has @',
|
||||
value: 'b@d',
|
||||
expectedError: 'parameter name must be alphanumeric but it was "b@d"',
|
||||
},
|
||||
{
|
||||
name: 'has {',
|
||||
value: 'b{a}d',
|
||||
expectedError: 'parameter name must be alphanumeric but it was "b{a}d"',
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// act
|
||||
const act = () => action(testCase.value);
|
||||
// assert
|
||||
expect(act).to.throw(testCase.expectedError);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user