Refactor code to comply with ESLint rules

Major refactoring using ESLint with rules from AirBnb and Vue.

Enable most of the ESLint rules and do necessary linting in the code.
Also add more information for rules that are disabled to describe what
they are and why they are disabled.

Allow logging (`console.log`) in test files, and in development mode
(e.g. when working with `npm run serve`), but disable it when
environment is production (as pre-configured by Vue). Also add flag
(`--mode production`) in `lint:eslint` command so production linting is
executed earlier in lifecycle.

Disable rules that requires a separate work. Such as ESLint rules that
are broken in TypeScript: no-useless-constructor (eslint/eslint#14118)
and no-shadow (eslint/eslint#13014).
This commit is contained in:
undergroundwires
2022-01-02 18:20:14 +01:00
parent 96265b75de
commit 5b1fbe1e2f
341 changed files with 16126 additions and 15101 deletions

View File

@@ -4,43 +4,47 @@ import { FunctionCallArgument } from '@/application/Parser/Script/Compiler/Funct
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);
});
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);
}
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);
}
}

View File

@@ -4,140 +4,140 @@ import { FunctionCallArgumentCollection } from '@/application/Parser/Script/Comp
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('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);
});
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);
});
}
});
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('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('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('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);
});
}
});
});
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);
});
}
});
});
});

View File

@@ -12,494 +12,498 @@ import { FunctionCallStub } from '@tests/unit/stubs/FunctionCallStub';
import { ExpressionsCompilerStub } from '@tests/unit/stubs/ExpressionsCompilerStub';
describe('FunctionCallCompiler', () => {
describe('compileCall', () => {
describe('parameter validation', () => {
describe('call', () => {
it('throws with undefined call', () => {
// arrange
const expectedError = 'undefined calls';
const call = undefined;
const functions = new SharedFunctionCollectionStub();
const sut = new MockableFunctionCallCompiler();
// act
const act = () => sut.compileCall(call, functions);
// assert
expect(act).to.throw(expectedError);
});
it('throws if call sequence has undefined call', () => {
// arrange
const expectedError = 'undefined function call';
const call = [
new FunctionCallStub(),
undefined,
];
const functions = new SharedFunctionCollectionStub();
const sut = new MockableFunctionCallCompiler();
// act
const act = () => sut.compileCall(call, functions);
// assert
expect(act).to.throw(expectedError);
});
describe('throws if call parameters does not match function parameters', () => {
// arrange
const functionName = 'test-function-name';
const testCases = [
{
name: 'provided: single unexpected parameter, when: another expected',
functionParameters: [ 'expected-parameter' ],
callParameters: [ 'unexpected-parameter' ],
expectedError: `Function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter"` +
`. Expected parameter(s): "expected-parameter"`
,
},
{
name: 'provided: multiple unexpected parameters, when: different one is expected',
functionParameters: [ 'expected-parameter' ],
callParameters: [ 'unexpected-parameter1', 'unexpected-parameter2' ],
expectedError: `Function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter1", "unexpected-parameter2"` +
`. Expected parameter(s): "expected-parameter"`
,
},
{
name: 'provided: an unexpected parameter, when: multiple parameters are expected',
functionParameters: [ 'expected-parameter1', 'expected-parameter2' ],
callParameters: [ 'expected-parameter1', 'expected-parameter2', 'unexpected-parameter' ],
expectedError: `Function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter"` +
`. Expected parameter(s): "expected-parameter1", "expected-parameter2"`,
},
{
name: 'provided: an unexpected parameter, when: none required',
functionParameters: undefined,
callParameters: [ 'unexpected-call-parameter' ],
expectedError: `Function "${functionName}" has unexpected parameter(s) provided: "unexpected-call-parameter"` +
`. Expected parameter(s): none`,
},
{
name: 'provided: expected and unexpected parameter, when: one of them is expected',
functionParameters: [ 'expected-parameter' ],
callParameters: [ 'expected-parameter', 'unexpected-parameter' ],
expectedError: `Function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter"` +
`. Expected parameter(s): "expected-parameter"`,
},
];
for (const testCase of testCases) {
it(testCase.name, () => {
const func = new SharedFunctionStub(FunctionBodyType.Code)
.withName('test-function-name')
.withParameterNames(...testCase.functionParameters);
let params: FunctionCallParametersData = {};
for (const parameter of testCase.callParameters) {
params = {...params, [parameter]: 'defined-parameter-value '};
}
const call = new FunctionCallStub()
.withFunctionName(func.name)
.withArguments(params);
const functions = new SharedFunctionCollectionStub()
.withFunction(func);
const sut = new MockableFunctionCallCompiler();
// act
const act = () => sut.compileCall([call], functions);
// assert
expect(act).to.throw(testCase.expectedError);
});
}
});
});
describe('functions', () => {
it('throws with undefined functions', () => {
// arrange
const expectedError = 'undefined functions';
const call = new FunctionCallStub();
const functions = undefined;
const sut = new MockableFunctionCallCompiler();
// act
const act = () => sut.compileCall([call], functions);
// assert
expect(act).to.throw(expectedError);
});
it('throws if function does not exist', () => {
// arrange
const expectedError = 'function does not exist';
const call = new FunctionCallStub();
const functions: ISharedFunctionCollection = {
getFunctionByName: () => { throw new Error(expectedError); },
};
const sut = new MockableFunctionCallCompiler();
// act
const act = () => sut.compileCall([call], functions);
// assert
expect(act).to.throw(expectedError);
});
});
describe('compileCall', () => {
describe('parameter validation', () => {
describe('call', () => {
it('throws with undefined call', () => {
// arrange
const expectedError = 'undefined calls';
const call = undefined;
const functions = new SharedFunctionCollectionStub();
const sut = new MockableFunctionCallCompiler();
// act
const act = () => sut.compileCall(call, functions);
// assert
expect(act).to.throw(expectedError);
});
describe('builds code as expected', () => {
describe('builds single call as expected', () => {
// arrange
const parametersTestCases = [
{
name: 'empty parameters',
parameters: [],
callArgs: { },
},
{
name: 'non-empty parameters',
parameters: [ 'param1', 'param2' ],
callArgs: { param1: 'value1', param2: 'value2' },
},
];
for (const testCase of parametersTestCases) {
it(testCase.name, () => {
const expected = {
execute: 'expected code (execute)',
revert: 'expected code (revert)',
};
const func = new SharedFunctionStub(FunctionBodyType.Code)
.withParameterNames(...testCase.parameters);
const functions = new SharedFunctionCollectionStub().withFunction(func);
const call = new FunctionCallStub()
.withFunctionName(func.name)
.withArguments(testCase.callArgs);
const args = new FunctionCallArgumentCollectionStub().withArguments(testCase.callArgs);
const expressionsCompilerMock = new ExpressionsCompilerStub()
.setup({ givenCode: func.body.code.do, givenArgs: args, result: expected.execute })
.setup({ givenCode: func.body.code.revert, givenArgs: args, result: expected.revert });
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
// act
const actual = sut.compileCall([call], functions);
// assert
expect(actual.code).to.equal(expected.execute);
expect(actual.revertCode).to.equal(expected.revert);
});
}
});
it('builds call sequence as expected', () => {
// arrange
const firstFunction = new SharedFunctionStub(FunctionBodyType.Code)
.withName('first-function-name')
.withCode('first-function-code')
.withRevertCode('first-function-revert-code');
const secondFunction = new SharedFunctionStub(FunctionBodyType.Code)
.withName('second-function-name')
.withParameterNames('testParameter')
.withCode('second-function-code')
.withRevertCode('second-function-revert-code');
const secondCallArguments = { testParameter: 'testValue' };
const calls = [
new FunctionCallStub().withFunctionName(firstFunction.name).withArguments({}),
new FunctionCallStub().withFunctionName(secondFunction.name).withArguments(secondCallArguments),
];
const firstFunctionCallArgs = new FunctionCallArgumentCollectionStub();
const secondFunctionCallArgs = new FunctionCallArgumentCollectionStub()
.withArguments(secondCallArguments);
const expressionsCompilerMock = new ExpressionsCompilerStub()
.setupToReturnFunctionCode(firstFunction, firstFunctionCallArgs)
.setupToReturnFunctionCode(secondFunction, secondFunctionCallArgs);
const expectedExecute = `${firstFunction.body.code.do}\n${secondFunction.body.code.do}`;
const expectedRevert = `${firstFunction.body.code.revert}\n${secondFunction.body.code.revert}`;
const functions = new SharedFunctionCollectionStub()
.withFunction(firstFunction)
.withFunction(secondFunction);
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
// act
const actual = sut.compileCall(calls, functions);
// assert
expect(actual.code).to.equal(expectedExecute);
expect(actual.revertCode).to.equal(expectedRevert);
});
describe('can compile a call tree (function calling another)', () => {
describe('single deep function call', () => {
it('builds 2nd level of depth without arguments', () => {
// arrange
const emptyArgs = new FunctionCallArgumentCollectionStub();
const deepFunctionName = 'deepFunction';
const functions = {
deep: new SharedFunctionStub(FunctionBodyType.Code)
.withName(deepFunctionName)
.withCode('deep function code')
.withRevertCode('deep function final code'),
front: new SharedFunctionStub(FunctionBodyType.Calls)
.withName('frontFunction')
.withCalls(new FunctionCallStub()
.withFunctionName(deepFunctionName)
.withArgumentCollection(emptyArgs),
),
};
const expected = {
code: 'final code',
revert: 'final revert code',
};
const expressionsCompilerMock = new ExpressionsCompilerStub()
.setup({
givenCode: functions.deep.body.code.do,
givenArgs: emptyArgs,
result: expected.code,
})
.setup({
givenCode: functions.deep.body.code.revert,
givenArgs: emptyArgs,
result: expected.revert,
});
const mainCall = new FunctionCallStub()
.withFunctionName(functions.front.name)
.withArgumentCollection(emptyArgs);
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
// act
const actual = sut.compileCall(
[mainCall],
new SharedFunctionCollectionStub().withFunction(functions.deep, functions.front),
);
// assert
expect(actual.code).to.equal(expected.code);
expect(actual.revertCode).to.equal(expected.revert);
});
it('builds 2nd level of depth by compiling arguments', () => {
// arrange
const scenario = {
front: {
functionName: 'frontFunction',
parameterName: 'frontFunctionParameterName',
args: {
fromMainCall: 'initial argument to be compiled',
toNextStatic: 'value from "front" to "deep" in function definition',
toNextCompiled: 'argument from "front" to "deep" (compiled)',
},
callArgs: {
initialFromMainCall: () => new FunctionCallArgumentCollectionStub()
.withArgument(scenario.front.parameterName, scenario.front.args.fromMainCall),
expectedCallDeep: () => new FunctionCallArgumentCollectionStub()
.withArgument(scenario.deep.parameterName, scenario.front.args.toNextCompiled),
},
getFunction: () => new SharedFunctionStub(FunctionBodyType.Calls)
.withName(scenario.front.functionName)
.withParameterNames(scenario.front.parameterName)
.withCalls(new FunctionCallStub()
.withFunctionName(scenario.deep.functionName)
.withArgument(scenario.deep.parameterName, scenario.front.args.toNextStatic),
),
},
deep: {
functionName: 'deepFunction',
parameterName: 'deepFunctionParameterName',
getFunction: () => new SharedFunctionStub(FunctionBodyType.Code)
.withName(scenario.deep.functionName)
.withParameterNames(scenario.deep.parameterName)
.withCode(`${scenario.deep.functionName} function code`)
.withRevertCode(`${scenario.deep.functionName} function revert code`),
},
};
const expected = {
code: 'final code',
revert: 'final revert code',
};
const expressionsCompilerMock = new ExpressionsCompilerStub()
.setup({ // Front ===args===> Deep
givenCode: scenario.front.args.toNextStatic,
givenArgs: scenario.front.callArgs.initialFromMainCall(),
result: scenario.front.args.toNextCompiled,
})
// set-up compiling of deep, compiled argument should be sent
.setup({
givenCode: scenario.deep.getFunction().body.code.do,
givenArgs: scenario.front.callArgs.expectedCallDeep(),
result: expected.code,
})
.setup({
givenCode: scenario.deep.getFunction().body.code.revert,
givenArgs: scenario.front.callArgs.expectedCallDeep(),
result: expected.revert,
});
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
// act
const actual = sut.compileCall(
[
new FunctionCallStub()
.withFunctionName(scenario.front.functionName)
.withArgumentCollection(scenario.front.callArgs.initialFromMainCall()),
],
new SharedFunctionCollectionStub().withFunction(
scenario.deep.getFunction(), scenario.front.getFunction()),
);
// assert
expect(actual.code).to.equal(expected.code);
expect(actual.revertCode).to.equal(expected.revert);
});
it('builds 3rd level of depth by compiling arguments', () => {
// arrange
const scenario = {
first: {
functionName: 'firstFunction',
parameter: 'firstParameter',
args: {
fromMainCall: 'initial argument to be compiled',
toNextStatic: 'value from "first" to "second" in function definition',
toNextCompiled: 'argument from "first" to "second" (compiled)',
},
callArgs: {
initialFromMainCall: () => new FunctionCallArgumentCollectionStub()
.withArgument(scenario.first.parameter, scenario.first.args.fromMainCall),
expectedToSecond: () => new FunctionCallArgumentCollectionStub()
.withArgument(scenario.second.parameter, scenario.first.args.toNextCompiled),
},
getFunction: () => new SharedFunctionStub(FunctionBodyType.Calls)
.withName(scenario.first.functionName)
.withParameterNames(scenario.first.parameter)
.withCalls(new FunctionCallStub()
.withFunctionName(scenario.second.functionName)
.withArgument(scenario.second.parameter, scenario.first.args.toNextStatic),
),
},
second: {
functionName: 'secondFunction',
parameter: 'secondParameter',
args: {
toNextCompiled: 'argument second to third (compiled)',
toNextStatic: 'calling second to third',
},
callArgs: {
expectedToThird: () => new FunctionCallArgumentCollectionStub()
.withArgument(scenario.third.parameter, scenario.second.args.toNextCompiled),
},
getFunction: () => new SharedFunctionStub(FunctionBodyType.Calls)
.withName(scenario.second.functionName)
.withParameterNames(scenario.second.parameter)
.withCalls(new FunctionCallStub()
.withFunctionName(scenario.third.functionName)
.withArgument(scenario.third.parameter, scenario.second.args.toNextStatic),
),
},
third: {
functionName: 'thirdFunction',
parameter: 'thirdParameter',
getFunction: () => new SharedFunctionStub(FunctionBodyType.Code)
.withName(scenario.third.functionName)
.withParameterNames(scenario.third.parameter)
.withCode(`${scenario.third.functionName} function code`)
.withRevertCode(`${scenario.third.functionName} function revert code`),
},
};
const expected = {
code: 'final code',
revert: 'final revert code',
};
const expressionsCompilerMock = new ExpressionsCompilerStub()
.setup({ // First ===args===> Second
givenCode: scenario.first.args.toNextStatic,
givenArgs: scenario.first.callArgs.initialFromMainCall(),
result: scenario.first.args.toNextCompiled,
})
.setup({ // Second ===args===> third
givenCode: scenario.second.args.toNextStatic,
givenArgs: scenario.first.callArgs.expectedToSecond(),
result: scenario.second.args.toNextCompiled,
})
// Compiling of third functions code with expected arguments
.setup({
givenCode: scenario.third.getFunction().body.code.do,
givenArgs: scenario.second.callArgs.expectedToThird(),
result: expected.code,
})
.setup({
givenCode: scenario.third.getFunction().body.code.revert,
givenArgs: scenario.second.callArgs.expectedToThird(),
result: expected.revert,
});
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
const mainCall = new FunctionCallStub()
.withFunctionName(scenario.first.functionName)
.withArgumentCollection(scenario.first.callArgs.initialFromMainCall());
// act
const actual = sut.compileCall(
[mainCall],
new SharedFunctionCollectionStub().withFunction(
scenario.first.getFunction(),
scenario.second.getFunction(),
scenario.third.getFunction()),
);
// assert
expect(actual.code).to.equal(expected.code);
expect(actual.revertCode).to.equal(expected.revert);
});
});
describe('multiple deep function calls', () => {
it('builds 2nd level of depth without arguments', () => {
// arrange
const emptyArgs = new FunctionCallArgumentCollectionStub();
const functions = {
call1: {
deep: {
functionName: 'deepFunction',
getFunction: () => new SharedFunctionStub(FunctionBodyType.Code)
.withName(functions.call1.deep.functionName)
.withCode('deep function (1) code')
.withRevertCode('deep function (1) final code'),
},
front: {
getFunction: () => new SharedFunctionStub(FunctionBodyType.Calls)
.withName('frontFunction')
.withCalls(new FunctionCallStub()
.withFunctionName(functions.call1.deep.functionName)
.withArgumentCollection(emptyArgs),
),
},
},
call2: {
deep: {
functionName: 'deepFunction2',
getFunction: () => new SharedFunctionStub(FunctionBodyType.Code)
.withName(functions.call2.deep.functionName)
.withCode('deep function (2) code')
.withRevertCode('deep function (2) final code'),
},
front: {
getFunction: () => new SharedFunctionStub(FunctionBodyType.Calls)
.withName('frontFunction2')
.withCalls(new FunctionCallStub()
.withFunctionName(functions.call2.deep.functionName)
.withArgumentCollection(emptyArgs),
),
},
},
getMainCall: () => [
new FunctionCallStub()
.withFunctionName(functions.call1.front.getFunction().name)
.withArgumentCollection(emptyArgs),
new FunctionCallStub()
.withFunctionName(functions.call2.front.getFunction().name)
.withArgumentCollection(emptyArgs),
],
getCollection: () => new SharedFunctionCollectionStub().withFunction(
functions.call1.deep.getFunction(),
functions.call1.front.getFunction(),
functions.call2.deep.getFunction(),
functions.call2.front.getFunction(),
),
};
const expressionsCompilerMock = new ExpressionsCompilerStub()
.setupToReturnFunctionCode(functions.call1.deep.getFunction(), emptyArgs)
.setupToReturnFunctionCode(functions.call2.deep.getFunction(), emptyArgs);
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
const expected = {
code: `${functions.call1.deep.getFunction().body.code.do}\n${functions.call2.deep.getFunction().body.code.do}`,
revert: `${functions.call1.deep.getFunction().body.code.revert}\n${functions.call2.deep.getFunction().body.code.revert}`,
};
// act
const actual = sut.compileCall(
functions.getMainCall(),
functions.getCollection(),
);
// assert
expect(actual.code).to.equal(expected.code);
expect(actual.revertCode).to.equal(expected.revert);
});
});
});
it('throws if call sequence has undefined call', () => {
// arrange
const expectedError = 'undefined function call';
const call = [
new FunctionCallStub(),
undefined,
];
const functions = new SharedFunctionCollectionStub();
const sut = new MockableFunctionCallCompiler();
// act
const act = () => sut.compileCall(call, functions);
// assert
expect(act).to.throw(expectedError);
});
describe('throws if call parameters does not match function parameters', () => {
// arrange
const functionName = 'test-function-name';
const testCases = [
{
name: 'provided: single unexpected parameter, when: another expected',
functionParameters: ['expected-parameter'],
callParameters: ['unexpected-parameter'],
expectedError:
`Function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter"`
+ '. Expected parameter(s): "expected-parameter"',
},
{
name: 'provided: multiple unexpected parameters, when: different one is expected',
functionParameters: ['expected-parameter'],
callParameters: ['unexpected-parameter1', 'unexpected-parameter2'],
expectedError:
`Function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter1", "unexpected-parameter2"`
+ '. Expected parameter(s): "expected-parameter"',
},
{
name: 'provided: an unexpected parameter, when: multiple parameters are expected',
functionParameters: ['expected-parameter1', 'expected-parameter2'],
callParameters: ['expected-parameter1', 'expected-parameter2', 'unexpected-parameter'],
expectedError:
`Function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter"`
+ '. Expected parameter(s): "expected-parameter1", "expected-parameter2"',
},
{
name: 'provided: an unexpected parameter, when: none required',
functionParameters: undefined,
callParameters: ['unexpected-call-parameter'],
expectedError:
`Function "${functionName}" has unexpected parameter(s) provided: "unexpected-call-parameter"`
+ '. Expected parameter(s): none',
},
{
name: 'provided: expected and unexpected parameter, when: one of them is expected',
functionParameters: ['expected-parameter'],
callParameters: ['expected-parameter', 'unexpected-parameter'],
expectedError:
`Function "${functionName}" has unexpected parameter(s) provided: "unexpected-parameter"`
+ '. Expected parameter(s): "expected-parameter"',
},
];
for (const testCase of testCases) {
it(testCase.name, () => {
const func = new SharedFunctionStub(FunctionBodyType.Code)
.withName('test-function-name')
.withParameterNames(...testCase.functionParameters);
let params: FunctionCallParametersData = {};
for (const parameter of testCase.callParameters) {
params = { ...params, [parameter]: 'defined-parameter-value ' };
}
const call = new FunctionCallStub()
.withFunctionName(func.name)
.withArguments(params);
const functions = new SharedFunctionCollectionStub()
.withFunction(func);
const sut = new MockableFunctionCallCompiler();
// act
const act = () => sut.compileCall([call], functions);
// assert
expect(act).to.throw(testCase.expectedError);
});
}
});
});
describe('functions', () => {
it('throws with undefined functions', () => {
// arrange
const expectedError = 'undefined functions';
const call = new FunctionCallStub();
const functions = undefined;
const sut = new MockableFunctionCallCompiler();
// act
const act = () => sut.compileCall([call], functions);
// assert
expect(act).to.throw(expectedError);
});
it('throws if function does not exist', () => {
// arrange
const expectedError = 'function does not exist';
const call = new FunctionCallStub();
const functions: ISharedFunctionCollection = {
getFunctionByName: () => { throw new Error(expectedError); },
};
const sut = new MockableFunctionCallCompiler();
// act
const act = () => sut.compileCall([call], functions);
// assert
expect(act).to.throw(expectedError);
});
});
});
describe('builds code as expected', () => {
describe('builds single call as expected', () => {
// arrange
const parametersTestCases = [
{
name: 'empty parameters',
parameters: [],
callArgs: { },
},
{
name: 'non-empty parameters',
parameters: ['param1', 'param2'],
callArgs: { param1: 'value1', param2: 'value2' },
},
];
for (const testCase of parametersTestCases) {
it(testCase.name, () => {
const expected = {
execute: 'expected code (execute)',
revert: 'expected code (revert)',
};
const func = new SharedFunctionStub(FunctionBodyType.Code)
.withParameterNames(...testCase.parameters);
const functions = new SharedFunctionCollectionStub().withFunction(func);
const call = new FunctionCallStub()
.withFunctionName(func.name)
.withArguments(testCase.callArgs);
const args = new FunctionCallArgumentCollectionStub().withArguments(testCase.callArgs);
const { code } = func.body;
const expressionsCompilerMock = new ExpressionsCompilerStub()
.setup({ givenCode: code.do, givenArgs: args, result: expected.execute })
.setup({ givenCode: code.revert, givenArgs: args, result: expected.revert });
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
// act
const actual = sut.compileCall([call], functions);
// assert
expect(actual.code).to.equal(expected.execute);
expect(actual.revertCode).to.equal(expected.revert);
});
}
});
it('builds call sequence as expected', () => {
// arrange
const firstFunction = new SharedFunctionStub(FunctionBodyType.Code)
.withName('first-function-name')
.withCode('first-function-code')
.withRevertCode('first-function-revert-code');
const secondFunction = new SharedFunctionStub(FunctionBodyType.Code)
.withName('second-function-name')
.withParameterNames('testParameter')
.withCode('second-function-code')
.withRevertCode('second-function-revert-code');
const secondCallArguments = { testParameter: 'testValue' };
const calls = [
new FunctionCallStub()
.withFunctionName(firstFunction.name)
.withArguments({}),
new FunctionCallStub()
.withFunctionName(secondFunction.name)
.withArguments(secondCallArguments),
];
const firstFunctionCallArgs = new FunctionCallArgumentCollectionStub();
const secondFunctionCallArgs = new FunctionCallArgumentCollectionStub()
.withArguments(secondCallArguments);
const expressionsCompilerMock = new ExpressionsCompilerStub()
.setupToReturnFunctionCode(firstFunction, firstFunctionCallArgs)
.setupToReturnFunctionCode(secondFunction, secondFunctionCallArgs);
const expectedExecute = `${firstFunction.body.code.do}\n${secondFunction.body.code.do}`;
const expectedRevert = `${firstFunction.body.code.revert}\n${secondFunction.body.code.revert}`;
const functions = new SharedFunctionCollectionStub()
.withFunction(firstFunction)
.withFunction(secondFunction);
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
// act
const actual = sut.compileCall(calls, functions);
// assert
expect(actual.code).to.equal(expectedExecute);
expect(actual.revertCode).to.equal(expectedRevert);
});
describe('can compile a call tree (function calling another)', () => {
describe('single deep function call', () => {
it('builds 2nd level of depth without arguments', () => {
// arrange
const emptyArgs = new FunctionCallArgumentCollectionStub();
const deepFunctionName = 'deepFunction';
const functions = {
deep: new SharedFunctionStub(FunctionBodyType.Code)
.withName(deepFunctionName)
.withCode('deep function code')
.withRevertCode('deep function final code'),
front: new SharedFunctionStub(FunctionBodyType.Calls)
.withName('frontFunction')
.withCalls(new FunctionCallStub()
.withFunctionName(deepFunctionName)
.withArgumentCollection(emptyArgs)),
};
const expected = {
code: 'final code',
revert: 'final revert code',
};
const expressionsCompilerMock = new ExpressionsCompilerStub()
.setup({
givenCode: functions.deep.body.code.do,
givenArgs: emptyArgs,
result: expected.code,
})
.setup({
givenCode: functions.deep.body.code.revert,
givenArgs: emptyArgs,
result: expected.revert,
});
const mainCall = new FunctionCallStub()
.withFunctionName(functions.front.name)
.withArgumentCollection(emptyArgs);
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
// act
const actual = sut.compileCall(
[mainCall],
new SharedFunctionCollectionStub().withFunction(functions.deep, functions.front),
);
// assert
expect(actual.code).to.equal(expected.code);
expect(actual.revertCode).to.equal(expected.revert);
});
it('builds 2nd level of depth by compiling arguments', () => {
// arrange
const scenario = {
front: {
functionName: 'frontFunction',
parameterName: 'frontFunctionParameterName',
args: {
fromMainCall: 'initial argument to be compiled',
toNextStatic: 'value from "front" to "deep" in function definition',
toNextCompiled: 'argument from "front" to "deep" (compiled)',
},
callArgs: {
initialFromMainCall: () => new FunctionCallArgumentCollectionStub()
.withArgument(scenario.front.parameterName, scenario.front.args.fromMainCall),
expectedCallDeep: () => new FunctionCallArgumentCollectionStub()
.withArgument(scenario.deep.parameterName, scenario.front.args.toNextCompiled),
},
getFunction: () => new SharedFunctionStub(FunctionBodyType.Calls)
.withName(scenario.front.functionName)
.withParameterNames(scenario.front.parameterName)
.withCalls(new FunctionCallStub()
.withFunctionName(scenario.deep.functionName)
.withArgument(scenario.deep.parameterName, scenario.front.args.toNextStatic)),
},
deep: {
functionName: 'deepFunction',
parameterName: 'deepFunctionParameterName',
getFunction: () => new SharedFunctionStub(FunctionBodyType.Code)
.withName(scenario.deep.functionName)
.withParameterNames(scenario.deep.parameterName)
.withCode(`${scenario.deep.functionName} function code`)
.withRevertCode(`${scenario.deep.functionName} function revert code`),
},
};
const expected = {
code: 'final code',
revert: 'final revert code',
};
const expressionsCompilerMock = new ExpressionsCompilerStub()
.setup({ // Front ===args===> Deep
givenCode: scenario.front.args.toNextStatic,
givenArgs: scenario.front.callArgs.initialFromMainCall(),
result: scenario.front.args.toNextCompiled,
})
// set-up compiling of deep, compiled argument should be sent
.setup({
givenCode: scenario.deep.getFunction().body.code.do,
givenArgs: scenario.front.callArgs.expectedCallDeep(),
result: expected.code,
})
.setup({
givenCode: scenario.deep.getFunction().body.code.revert,
givenArgs: scenario.front.callArgs.expectedCallDeep(),
result: expected.revert,
});
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
// act
const actual = sut.compileCall(
[
new FunctionCallStub()
.withFunctionName(scenario.front.functionName)
.withArgumentCollection(scenario.front.callArgs.initialFromMainCall()),
],
new SharedFunctionCollectionStub().withFunction(
scenario.deep.getFunction(),
scenario.front.getFunction(),
),
);
// assert
expect(actual.code).to.equal(expected.code);
expect(actual.revertCode).to.equal(expected.revert);
});
it('builds 3rd level of depth by compiling arguments', () => {
// arrange
const scenario = {
first: {
functionName: 'firstFunction',
parameter: 'firstParameter',
args: {
fromMainCall: 'initial argument to be compiled',
toNextStatic: 'value from "first" to "second" in function definition',
toNextCompiled: 'argument from "first" to "second" (compiled)',
},
callArgs: {
initialFromMainCall: () => new FunctionCallArgumentCollectionStub()
.withArgument(scenario.first.parameter, scenario.first.args.fromMainCall),
expectedToSecond: () => new FunctionCallArgumentCollectionStub()
.withArgument(scenario.second.parameter, scenario.first.args.toNextCompiled),
},
getFunction: () => new SharedFunctionStub(FunctionBodyType.Calls)
.withName(scenario.first.functionName)
.withParameterNames(scenario.first.parameter)
.withCalls(new FunctionCallStub()
.withFunctionName(scenario.second.functionName)
.withArgument(scenario.second.parameter, scenario.first.args.toNextStatic)),
},
second: {
functionName: 'secondFunction',
parameter: 'secondParameter',
args: {
toNextCompiled: 'argument second to third (compiled)',
toNextStatic: 'calling second to third',
},
callArgs: {
expectedToThird: () => new FunctionCallArgumentCollectionStub()
.withArgument(scenario.third.parameter, scenario.second.args.toNextCompiled),
},
getFunction: () => new SharedFunctionStub(FunctionBodyType.Calls)
.withName(scenario.second.functionName)
.withParameterNames(scenario.second.parameter)
.withCalls(new FunctionCallStub()
.withFunctionName(scenario.third.functionName)
.withArgument(scenario.third.parameter, scenario.second.args.toNextStatic)),
},
third: {
functionName: 'thirdFunction',
parameter: 'thirdParameter',
getFunction: () => new SharedFunctionStub(FunctionBodyType.Code)
.withName(scenario.third.functionName)
.withParameterNames(scenario.third.parameter)
.withCode(`${scenario.third.functionName} function code`)
.withRevertCode(`${scenario.third.functionName} function revert code`),
},
};
const expected = {
code: 'final code',
revert: 'final revert code',
};
const expressionsCompilerMock = new ExpressionsCompilerStub()
.setup({ // First ===args===> Second
givenCode: scenario.first.args.toNextStatic,
givenArgs: scenario.first.callArgs.initialFromMainCall(),
result: scenario.first.args.toNextCompiled,
})
.setup({ // Second ===args===> third
givenCode: scenario.second.args.toNextStatic,
givenArgs: scenario.first.callArgs.expectedToSecond(),
result: scenario.second.args.toNextCompiled,
})
// Compiling of third functions code with expected arguments
.setup({
givenCode: scenario.third.getFunction().body.code.do,
givenArgs: scenario.second.callArgs.expectedToThird(),
result: expected.code,
})
.setup({
givenCode: scenario.third.getFunction().body.code.revert,
givenArgs: scenario.second.callArgs.expectedToThird(),
result: expected.revert,
});
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
const mainCall = new FunctionCallStub()
.withFunctionName(scenario.first.functionName)
.withArgumentCollection(scenario.first.callArgs.initialFromMainCall());
// act
const actual = sut.compileCall(
[mainCall],
new SharedFunctionCollectionStub().withFunction(
scenario.first.getFunction(),
scenario.second.getFunction(),
scenario.third.getFunction(),
),
);
// assert
expect(actual.code).to.equal(expected.code);
expect(actual.revertCode).to.equal(expected.revert);
});
});
describe('multiple deep function calls', () => {
it('builds 2nd level of depth without arguments', () => {
// arrange
const emptyArgs = new FunctionCallArgumentCollectionStub();
const functions = {
call1: {
deep: {
functionName: 'deepFunction',
getFunction: () => new SharedFunctionStub(FunctionBodyType.Code)
.withName(functions.call1.deep.functionName)
.withCode('deep function (1) code')
.withRevertCode('deep function (1) final code'),
},
front: {
getFunction: () => new SharedFunctionStub(FunctionBodyType.Calls)
.withName('frontFunction')
.withCalls(new FunctionCallStub()
.withFunctionName(functions.call1.deep.functionName)
.withArgumentCollection(emptyArgs)),
},
},
call2: {
deep: {
functionName: 'deepFunction2',
getFunction: () => new SharedFunctionStub(FunctionBodyType.Code)
.withName(functions.call2.deep.functionName)
.withCode('deep function (2) code')
.withRevertCode('deep function (2) final code'),
},
front: {
getFunction: () => new SharedFunctionStub(FunctionBodyType.Calls)
.withName('frontFunction2')
.withCalls(new FunctionCallStub()
.withFunctionName(functions.call2.deep.functionName)
.withArgumentCollection(emptyArgs)),
},
},
getMainCall: () => [
new FunctionCallStub()
.withFunctionName(functions.call1.front.getFunction().name)
.withArgumentCollection(emptyArgs),
new FunctionCallStub()
.withFunctionName(functions.call2.front.getFunction().name)
.withArgumentCollection(emptyArgs),
],
getCollection: () => new SharedFunctionCollectionStub().withFunction(
functions.call1.deep.getFunction(),
functions.call1.front.getFunction(),
functions.call2.deep.getFunction(),
functions.call2.front.getFunction(),
),
};
const expressionsCompilerMock = new ExpressionsCompilerStub()
.setupToReturnFunctionCode(functions.call1.deep.getFunction(), emptyArgs)
.setupToReturnFunctionCode(functions.call2.deep.getFunction(), emptyArgs);
const sut = new MockableFunctionCallCompiler(expressionsCompilerMock);
const expected = {
code: `${functions.call1.deep.getFunction().body.code.do}\n${functions.call2.deep.getFunction().body.code.do}`,
revert: `${functions.call1.deep.getFunction().body.code.revert}\n${functions.call2.deep.getFunction().body.code.revert}`,
};
// act
const actual = sut.compileCall(
functions.getMainCall(),
functions.getCollection(),
);
// assert
expect(actual.code).to.equal(expected.code);
expect(actual.revertCode).to.equal(expected.revert);
});
});
});
});
});
});
class MockableFunctionCallCompiler extends FunctionCallCompiler {
constructor(expressionsCompiler: IExpressionsCompiler = new ExpressionsCompilerStub()) {
super(expressionsCompiler);
}
constructor(expressionsCompiler: IExpressionsCompiler = new ExpressionsCompilerStub()) {
super(expressionsCompiler);
}
}

View File

@@ -5,72 +5,73 @@ import { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Sc
import { FunctionCallArgumentCollectionStub } from '@tests/unit/stubs/FunctionCallArgumentCollectionStub';
describe('FunctionCall', () => {
describe('ctor', () => {
describe('args', () => {
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('sets args as expected', () => {
// arrange
const expected = new FunctionCallArgumentCollectionStub()
.withArgument('testParameter', 'testValue');
// act
const sut = new FunctionCallBuilder()
.withArgs(expected)
.build();
// assert
expect(sut.args).to.deep.equal(expected);
});
});
describe('functionName', () => {
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);
});
it('sets function name as expected', () => {
// arrange
const expected = 'expectedFunctionName';
// act
const sut = new FunctionCallBuilder()
.withFunctionName(expected)
.build();
// assert
expect(sut.functionName).to.equal(expected);
});
});
describe('ctor', () => {
describe('args', () => {
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('sets args as expected', () => {
// arrange
const expected = new FunctionCallArgumentCollectionStub()
.withArgument('testParameter', 'testValue');
// act
const sut = new FunctionCallBuilder()
.withArgs(expected)
.build();
// assert
expect(sut.args).to.deep.equal(expected);
});
});
describe('functionName', () => {
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);
});
it('sets function name as expected', () => {
// arrange
const expected = 'expectedFunctionName';
// act
const sut = new FunctionCallBuilder()
.withFunctionName(expected)
.build();
// assert
expect(sut.functionName).to.equal(expected);
});
});
});
});
class FunctionCallBuilder {
private functionName = 'functionName';
private args: IReadOnlyFunctionCallArgumentCollection = new FunctionCallArgumentCollectionStub();
private functionName = 'functionName';
public withFunctionName(functionName: string) {
this.functionName = functionName;
return this;
}
private args: IReadOnlyFunctionCallArgumentCollection = new FunctionCallArgumentCollectionStub();
public withArgs(args: IReadOnlyFunctionCallArgumentCollection) {
this.args = args;
return this;
}
public withFunctionName(functionName: string) {
this.functionName = functionName;
return this;
}
public build() {
return new FunctionCall(this.functionName, this.args);
}
public withArgs(args: IReadOnlyFunctionCallArgumentCollection) {
this.args = args;
return this;
}
public build() {
return new FunctionCall(this.functionName, this.args);
}
}

View File

@@ -4,98 +4,100 @@ import { parseFunctionCalls } from '@/application/Parser/Script/Compiler/Functio
import { FunctionCallDataStub } from '@tests/unit/stubs/FunctionCallDataStub';
describe('FunctionCallParser', () => {
describe('parseFunctionCalls', () => {
it('throws with undefined call', () => {
// arrange
const expectedError = 'undefined call data';
const call = undefined;
// act
const act = () => parseFunctionCalls(call);
// assert
expect(act).to.throw(expectedError);
});
it('throws if call is not an object', () => {
// arrange
const expectedError = 'called function(s) must be an object';
const invalidCalls: readonly any[] = ['string', 33];
invalidCalls.forEach((invalidCall) => {
// act
const act = () => parseFunctionCalls(invalidCall);
// assert
expect(act).to.throw(expectedError);
});
});
it('throws if call sequence has undefined call', () => {
// arrange
const expectedError = 'undefined function call';
const data = [
new FunctionCallDataStub(),
undefined,
];
// act
const act = () => parseFunctionCalls(data);
// assert
expect(act).to.throw(expectedError);
});
it('throws if call sequence has undefined function name', () => {
// arrange
const expectedError = 'empty function name in function call';
const data = [
new FunctionCallDataStub().withName('function-name'),
new FunctionCallDataStub().withName(undefined),
];
// act
const act = () => parseFunctionCalls(data);
// assert
expect(act).to.throw(expectedError);
});
it('parses single call as expected', () => {
// arrange
const expectedFunctionName = 'functionName';
const expectedParameterName = 'parameterName';
const expectedArgumentValue = 'argumentValue';
const data = new FunctionCallDataStub()
.withName(expectedFunctionName)
.withParameters({ [expectedParameterName]: expectedArgumentValue });
// act
const actual = parseFunctionCalls(data);
// assert
expect(actual).to.have.lengthOf(1);
const call = actual[0];
expect(call.functionName).to.equal(expectedFunctionName);
const args = call.args;
expect(args.getAllParameterNames()).to.have.lengthOf(1);
expect(args.hasArgument(expectedParameterName)).to.equal(true,
`Does not include expected parameter: "${expectedParameterName}"\n` +
`But includes: "${args.getAllParameterNames()}"`);
const argument = args.getArgument(expectedParameterName);
expect(argument.parameterName).to.equal(expectedParameterName);
expect(argument.argumentValue).to.equal(expectedArgumentValue);
});
it('parses multiple calls as expected', () => {
// arrange
const getFunctionName = (index: number) => `functionName${index}`;
const getParameterName = (index: number) => `parameterName${index}`;
const getArgumentValue = (index: number) => `argumentValue${index}`;
const createCall = (index: number) => new FunctionCallDataStub()
.withName(getFunctionName(index))
.withParameters({ [getParameterName(index)]: getArgumentValue(index)});
const calls = [ createCall(0), createCall(1), createCall(2), createCall(3) ];
// act
const actual = parseFunctionCalls(calls);
// assert
expect(actual).to.have.lengthOf(calls.length);
for (let i = 0; i < calls.length; i++) {
const call = actual[i];
const expectedParameterName = getParameterName(i);
const expectedArgumentValue = getArgumentValue(i);
expect(call.functionName).to.equal(getFunctionName(i));
expect(call.args.getAllParameterNames()).to.have.lengthOf(1);
expect(call.args.hasArgument(expectedParameterName)).to.equal(true);
const argument = call.args.getArgument(expectedParameterName);
expect(argument.parameterName).to.equal(expectedParameterName);
expect(argument.argumentValue).to.equal(expectedArgumentValue);
}
});
describe('parseFunctionCalls', () => {
it('throws with undefined call', () => {
// arrange
const expectedError = 'undefined call data';
const call = undefined;
// act
const act = () => parseFunctionCalls(call);
// assert
expect(act).to.throw(expectedError);
});
it('throws if call is not an object', () => {
// arrange
const expectedError = 'called function(s) must be an object';
const invalidCalls = ['string', 33, false];
invalidCalls.forEach((invalidCall) => {
// act
const act = () => parseFunctionCalls(invalidCall as never);
// assert
expect(act).to.throw(expectedError);
});
});
it('throws if call sequence has undefined call', () => {
// arrange
const expectedError = 'undefined function call';
const data = [
new FunctionCallDataStub(),
undefined,
];
// act
const act = () => parseFunctionCalls(data);
// assert
expect(act).to.throw(expectedError);
});
it('throws if call sequence has undefined function name', () => {
// arrange
const expectedError = 'empty function name in function call';
const data = [
new FunctionCallDataStub().withName('function-name'),
new FunctionCallDataStub().withName(undefined),
];
// act
const act = () => parseFunctionCalls(data);
// assert
expect(act).to.throw(expectedError);
});
it('parses single call as expected', () => {
// arrange
const expectedFunctionName = 'functionName';
const expectedParameterName = 'parameterName';
const expectedArgumentValue = 'argumentValue';
const data = new FunctionCallDataStub()
.withName(expectedFunctionName)
.withParameters({ [expectedParameterName]: expectedArgumentValue });
// act
const actual = parseFunctionCalls(data);
// assert
expect(actual).to.have.lengthOf(1);
const call = actual[0];
expect(call.functionName).to.equal(expectedFunctionName);
const { args } = call;
expect(args.getAllParameterNames()).to.have.lengthOf(1);
expect(args.hasArgument(expectedParameterName)).to.equal(
true,
`Does not include expected parameter: "${expectedParameterName}"\n`
+ `But includes: "${args.getAllParameterNames()}"`,
);
const argument = args.getArgument(expectedParameterName);
expect(argument.parameterName).to.equal(expectedParameterName);
expect(argument.argumentValue).to.equal(expectedArgumentValue);
});
it('parses multiple calls as expected', () => {
// arrange
const getFunctionName = (index: number) => `functionName${index}`;
const getParameterName = (index: number) => `parameterName${index}`;
const getArgumentValue = (index: number) => `argumentValue${index}`;
const createCall = (index: number) => new FunctionCallDataStub()
.withName(getFunctionName(index))
.withParameters({ [getParameterName(index)]: getArgumentValue(index) });
const calls = [createCall(0), createCall(1), createCall(2), createCall(3)];
// act
const actual = parseFunctionCalls(calls);
// assert
expect(actual).to.have.lengthOf(calls.length);
for (let i = 0; i < calls.length; i++) {
const call = actual[i];
const expectedParameterName = getParameterName(i);
const expectedArgumentValue = getArgumentValue(i);
expect(call.functionName).to.equal(getFunctionName(i));
expect(call.args.getAllParameterNames()).to.have.lengthOf(1);
expect(call.args.hasArgument(expectedParameterName)).to.equal(true);
const argument = call.args.getArgument(expectedParameterName);
expect(argument.parameterName).to.equal(expectedParameterName);
expect(argument.argumentValue).to.equal(expectedArgumentValue);
}
});
});
});

View File

@@ -4,44 +4,48 @@ import { FunctionParameter } from '@/application/Parser/Script/Compiler/Function
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);
});
}
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);
}
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);
}
}

View File

@@ -4,44 +4,45 @@ import { FunctionParameterCollection } from '@/application/Parser/Script/Compile
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);
});
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);
});
});
});

View File

@@ -5,247 +5,256 @@ import { FunctionParameterCollectionStub } from '@tests/unit/stubs/FunctionParam
import { createCallerFunction, createFunctionWithInlineCode } from '@/application/Parser/Script/Compiler/Function/SharedFunction';
import { IFunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/IFunctionCall';
import { FunctionCallStub } from '@tests/unit/stubs/FunctionCallStub';
import { FunctionBodyType } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
import { ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
import { FunctionBodyType, ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
describe('SharedFunction', () => {
describe('name', () => {
runForEachFactoryMethod((build) => {
it('sets as expected', () => {
// arrange
const expected = 'expected-function-name';
const builder = new SharedFunctionBuilder()
.withName(expected);
// act
const sut = build(builder);
// assert
expect(sut.name).equal(expected);
});
it('throws if empty or undefined', () => {
// arrange
const expectedError = 'undefined function name';
const invalidValues = [ undefined, '' ];
for (const invalidValue of invalidValues) {
const builder = new SharedFunctionBuilder()
.withName(invalidValue);
// act
const act = () => build(builder);
// assert
expect(act).to.throw(expectedError);
}
});
});
describe('name', () => {
runForEachFactoryMethod((build) => {
it('sets as expected', () => {
// arrange
const expected = 'expected-function-name';
const builder = new SharedFunctionBuilder()
.withName(expected);
// act
const sut = build(builder);
// assert
expect(sut.name).equal(expected);
});
it('throws if empty or undefined', () => {
// arrange
const expectedError = 'undefined function name';
const invalidValues = [undefined, ''];
for (const invalidValue of invalidValues) {
const builder = new SharedFunctionBuilder()
.withName(invalidValue);
// act
const act = () => build(builder);
// assert
expect(act).to.throw(expectedError);
}
});
});
describe('parameters', () => {
runForEachFactoryMethod((build) => {
it('sets as expected', () => {
// arrange
const expected = new FunctionParameterCollectionStub()
.withParameterName('test-parameter');
const builder = new SharedFunctionBuilder()
.withParameters(expected);
// act
const sut = build(builder);
// assert
expect(sut.parameters).equal(expected);
});
it('throws if undefined', () => {
// arrange
const expectedError = 'undefined parameters';
const parameters = undefined;
const builder = new SharedFunctionBuilder()
.withParameters(parameters);
// act
const act = () => build(builder);
// assert
expect(act).to.throw(expectedError);
});
});
});
describe('parameters', () => {
runForEachFactoryMethod((build) => {
it('sets as expected', () => {
// arrange
const expected = new FunctionParameterCollectionStub()
.withParameterName('test-parameter');
const builder = new SharedFunctionBuilder()
.withParameters(expected);
// act
const sut = build(builder);
// assert
expect(sut.parameters).equal(expected);
});
it('throws if undefined', () => {
// arrange
const expectedError = 'undefined parameters';
const parameters = undefined;
const builder = new SharedFunctionBuilder()
.withParameters(parameters);
// act
const act = () => build(builder);
// assert
expect(act).to.throw(expectedError);
});
});
describe('body', () => {
describe('createFunctionWithInlineCode', () => {
describe('code', () => {
it('sets as expected', () => {
// arrange
const expected = 'expected-code';
// act
const sut = new SharedFunctionBuilder()
.withCode(expected)
.createFunctionWithInlineCode();
// assert
expect(sut.body.code.do).equal(expected);
});
it('throws if empty or undefined', () => {
// arrange
const functionName = 'expected-function-name';
const expectedError = `undefined code in function "${functionName}"`;
const invalidValues = [ undefined, '' ];
for (const invalidValue of invalidValues) {
// act
const act = () => new SharedFunctionBuilder()
.withName(functionName)
.withCode(invalidValue)
.createFunctionWithInlineCode();
// assert
expect(act).to.throw(expectedError);
}
});
});
describe('revertCode', () => {
it('sets as expected', () => {
// arrange
const testData = [ 'expected-revert-code', undefined, '' ];
for (const data of testData) {
// act
const sut = new SharedFunctionBuilder()
.withRevertCode(data)
.createFunctionWithInlineCode();
// assert
expect(sut.body.code.revert).equal(data);
}
});
});
it('sets type as expected', () => {
// arrange
const expectedType = FunctionBodyType.Code;
// act
const sut = new SharedFunctionBuilder()
.createFunctionWithInlineCode();
// assert
expect(sut.body.type).equal(expectedType);
});
it('calls are undefined', () => {
// arrange
const expectedCalls = undefined;
// act
const sut = new SharedFunctionBuilder()
.createFunctionWithInlineCode();
// assert
expect(sut.body.calls).equal(expectedCalls);
});
});
describe('body', () => {
describe('createFunctionWithInlineCode', () => {
describe('code', () => {
it('sets as expected', () => {
// arrange
const expected = 'expected-code';
// act
const sut = new SharedFunctionBuilder()
.withCode(expected)
.createFunctionWithInlineCode();
// assert
expect(sut.body.code.do).equal(expected);
});
describe('createCallerFunction', () => {
describe('callSequence', () => {
it('sets as expected', () => {
// arrange
const expected = [
new FunctionCallStub().withFunctionName('firstFunction'),
new FunctionCallStub().withFunctionName('secondFunction'),
];
// act
const sut = new SharedFunctionBuilder()
.withCallSequence(expected)
.createCallerFunction();
// assert
expect(sut.body.calls).equal(expected);
});
it('throws if undefined', () => {
// arrange
const functionName = 'invalidFunction';
const callSequence = undefined;
const expectedError = `undefined call sequence in function "${functionName}"`;
// act
const act = () => new SharedFunctionBuilder()
.withName(functionName)
.withCallSequence(callSequence)
.createCallerFunction();
// assert
expect(act).to.throw(expectedError);
});
it('throws if empty', () => {
// arrange
const functionName = 'invalidFunction';
const callSequence = [ ];
const expectedError = `empty call sequence in function "${functionName}"`;
// act
const act = () => new SharedFunctionBuilder()
.withName(functionName)
.withCallSequence(callSequence)
.createCallerFunction();
// assert
expect(act).to.throw(expectedError);
});
});
it('sets type as expected', () => {
// arrange
const expectedType = FunctionBodyType.Calls;
// act
const sut = new SharedFunctionBuilder()
.createCallerFunction();
// assert
expect(sut.body.type).equal(expectedType);
});
it('code is undefined', () => {
// arrange
const expectedCode = undefined;
// act
const sut = new SharedFunctionBuilder()
.createCallerFunction();
// assert
expect(sut.body.code).equal(expectedCode);
});
it('throws if empty or undefined', () => {
// arrange
const functionName = 'expected-function-name';
const expectedError = `undefined code in function "${functionName}"`;
const invalidValues = [undefined, ''];
for (const invalidValue of invalidValues) {
// act
const act = () => new SharedFunctionBuilder()
.withName(functionName)
.withCode(invalidValue)
.createFunctionWithInlineCode();
// assert
expect(act).to.throw(expectedError);
}
});
});
describe('revertCode', () => {
it('sets as expected', () => {
// arrange
const testData = ['expected-revert-code', undefined, ''];
for (const data of testData) {
// act
const sut = new SharedFunctionBuilder()
.withRevertCode(data)
.createFunctionWithInlineCode();
// assert
expect(sut.body.code.revert).equal(data);
}
});
});
it('sets type as expected', () => {
// arrange
const expectedType = FunctionBodyType.Code;
// act
const sut = new SharedFunctionBuilder()
.createFunctionWithInlineCode();
// assert
expect(sut.body.type).equal(expectedType);
});
it('calls are undefined', () => {
// arrange
const expectedCalls = undefined;
// act
const sut = new SharedFunctionBuilder()
.createFunctionWithInlineCode();
// assert
expect(sut.body.calls).equal(expectedCalls);
});
});
describe('createCallerFunction', () => {
describe('callSequence', () => {
it('sets as expected', () => {
// arrange
const expected = [
new FunctionCallStub().withFunctionName('firstFunction'),
new FunctionCallStub().withFunctionName('secondFunction'),
];
// act
const sut = new SharedFunctionBuilder()
.withCallSequence(expected)
.createCallerFunction();
// assert
expect(sut.body.calls).equal(expected);
});
it('throws if undefined', () => {
// arrange
const functionName = 'invalidFunction';
const callSequence = undefined;
const expectedError = `undefined call sequence in function "${functionName}"`;
// act
const act = () => new SharedFunctionBuilder()
.withName(functionName)
.withCallSequence(callSequence)
.createCallerFunction();
// assert
expect(act).to.throw(expectedError);
});
it('throws if empty', () => {
// arrange
const functionName = 'invalidFunction';
const callSequence = [];
const expectedError = `empty call sequence in function "${functionName}"`;
// act
const act = () => new SharedFunctionBuilder()
.withName(functionName)
.withCallSequence(callSequence)
.createCallerFunction();
// assert
expect(act).to.throw(expectedError);
});
});
it('sets type as expected', () => {
// arrange
const expectedType = FunctionBodyType.Calls;
// act
const sut = new SharedFunctionBuilder()
.createCallerFunction();
// assert
expect(sut.body.type).equal(expectedType);
});
it('code is undefined', () => {
// arrange
const expectedCode = undefined;
// act
const sut = new SharedFunctionBuilder()
.createCallerFunction();
// assert
expect(sut.body.code).equal(expectedCode);
});
});
});
});
function runForEachFactoryMethod(
act: (action: (sut: SharedFunctionBuilder) => ISharedFunction) => void): void {
describe('createCallerFunction', () => {
const action = (builder: SharedFunctionBuilder) => builder.createCallerFunction();
act(action);
});
describe('createFunctionWithInlineCode', () => {
const action = (builder: SharedFunctionBuilder) => builder.createFunctionWithInlineCode();
act(action);
});
act: (action: (sut: SharedFunctionBuilder) => ISharedFunction) => void,
): void {
describe('createCallerFunction', () => {
const action = (builder: SharedFunctionBuilder) => builder.createCallerFunction();
act(action);
});
describe('createFunctionWithInlineCode', () => {
const action = (builder: SharedFunctionBuilder) => builder.createFunctionWithInlineCode();
act(action);
});
}
/*
Using an abstraction here allows for easy refactorings in
parameters or moving between functional and object-oriented
solutions without refactorings all tests.
Using an abstraction here allows for easy refactorings in
parameters or moving between functional and object-oriented
solutions without refactorings all tests.
*/
class SharedFunctionBuilder {
private name = 'name';
private parameters: IReadOnlyFunctionParameterCollection = new FunctionParameterCollectionStub();
private callSequence: readonly IFunctionCall[] = [ new FunctionCallStub() ];
private code = 'code';
private revertCode = 'revert-code';
private name = 'name';
public createCallerFunction(): ISharedFunction {
return createCallerFunction(
this.name,
this.parameters,
this.callSequence,
);
}
public createFunctionWithInlineCode(): ISharedFunction {
return createFunctionWithInlineCode(
this.name,
this.parameters,
this.code,
this.revertCode,
);
}
private parameters: IReadOnlyFunctionParameterCollection = new FunctionParameterCollectionStub();
public withName(name: string) {
this.name = name;
return this;
}
public withParameters(parameters: IReadOnlyFunctionParameterCollection) {
this.parameters = parameters;
return this;
}
public withCode(code: string) {
this.code = code;
return this;
}
public withRevertCode(revertCode: string) {
this.revertCode = revertCode;
return this;
}
public withCallSequence(callSequence: readonly IFunctionCall[]) {
this.callSequence = callSequence;
return this;
}
private callSequence: readonly IFunctionCall[] = [new FunctionCallStub()];
private code = 'code';
private revertCode = 'revert-code';
public createCallerFunction(): ISharedFunction {
return createCallerFunction(
this.name,
this.parameters,
this.callSequence,
);
}
public createFunctionWithInlineCode(): ISharedFunction {
return createFunctionWithInlineCode(
this.name,
this.parameters,
this.code,
this.revertCode,
);
}
public withName(name: string) {
this.name = name;
return this;
}
public withParameters(parameters: IReadOnlyFunctionParameterCollection) {
this.parameters = parameters;
return this;
}
public withCode(code: string) {
this.code = code;
return this;
}
public withRevertCode(revertCode: string) {
this.revertCode = revertCode;
return this;
}
public withCallSequence(callSequence: readonly IFunctionCall[]) {
this.callSequence = callSequence;
return this;
}
}

View File

@@ -6,85 +6,85 @@ import { FunctionBodyType } from '@/application/Parser/Script/Compiler/Function/
import { FunctionCallStub } from '@tests/unit/stubs/FunctionCallStub';
describe('SharedFunctionCollection', () => {
describe('addFunction', () => {
it('throws if function is undefined', () => {
// arrange
const expectedError = 'undefined function';
const func = undefined;
const sut = new SharedFunctionCollection();
// act
const act = () => sut.addFunction(func);
// assert
expect(act).to.throw(expectedError);
});
it('throws if function with same name already exists', () => {
// arrange
const functionName = 'duplicate-function';
const expectedError = `function with name ${functionName} already exists`;
const func = new SharedFunctionStub(FunctionBodyType.Code)
.withName('duplicate-function');
const sut = new SharedFunctionCollection();
sut.addFunction(func);
// act
const act = () => sut.addFunction(func);
// assert
expect(act).to.throw(expectedError);
});
describe('addFunction', () => {
it('throws if function is undefined', () => {
// arrange
const expectedError = 'undefined function';
const func = undefined;
const sut = new SharedFunctionCollection();
// act
const act = () => sut.addFunction(func);
// assert
expect(act).to.throw(expectedError);
});
describe('getFunctionByName', () => {
it('throws if name is undefined', () => {
// arrange
const expectedError = 'undefined function name';
const invalidValues = [ undefined, '' ];
const sut = new SharedFunctionCollection();
for (const invalidValue of invalidValues) {
const name = invalidValue;
// act
const act = () => sut.getFunctionByName(name);
// assert
expect(act).to.throw(expectedError);
}
});
it('throws if function does not exist', () => {
// arrange
const name = 'unique-name';
const expectedError = `called function is not defined "${name}"`;
const func = new SharedFunctionStub(FunctionBodyType.Code)
.withName('unexpected-name');
const sut = new SharedFunctionCollection();
sut.addFunction(func);
// act
const act = () => sut.getFunctionByName(name);
// assert
expect(act).to.throw(expectedError);
});
describe('returns existing function', () => {
it('when function with inline code is added', () => {
// arrange
const expected = new SharedFunctionStub(FunctionBodyType.Code)
.withName('expected-function-name');
const sut = new SharedFunctionCollection();
// act
sut.addFunction(expected);
const actual = sut.getFunctionByName(expected.name);
// assert
expect(actual).to.equal(expected);
});
it('when calling function is added', () => {
// arrange
const callee = new SharedFunctionStub(FunctionBodyType.Code)
.withName('calleeFunction');
const caller = new SharedFunctionStub(FunctionBodyType.Calls)
.withName('callerFunction')
.withCalls(new FunctionCallStub().withFunctionName(callee.name));
const sut = new SharedFunctionCollection();
// act
sut.addFunction(callee);
sut.addFunction(caller);
const actual = sut.getFunctionByName(caller.name);
// assert
expect(actual).to.equal(caller);
});
});
it('throws if function with same name already exists', () => {
// arrange
const functionName = 'duplicate-function';
const expectedError = `function with name ${functionName} already exists`;
const func = new SharedFunctionStub(FunctionBodyType.Code)
.withName('duplicate-function');
const sut = new SharedFunctionCollection();
sut.addFunction(func);
// act
const act = () => sut.addFunction(func);
// assert
expect(act).to.throw(expectedError);
});
});
describe('getFunctionByName', () => {
it('throws if name is undefined', () => {
// arrange
const expectedError = 'undefined function name';
const invalidValues = [undefined, ''];
const sut = new SharedFunctionCollection();
for (const invalidValue of invalidValues) {
const name = invalidValue;
// act
const act = () => sut.getFunctionByName(name);
// assert
expect(act).to.throw(expectedError);
}
});
it('throws if function does not exist', () => {
// arrange
const name = 'unique-name';
const expectedError = `called function is not defined "${name}"`;
const func = new SharedFunctionStub(FunctionBodyType.Code)
.withName('unexpected-name');
const sut = new SharedFunctionCollection();
sut.addFunction(func);
// act
const act = () => sut.getFunctionByName(name);
// assert
expect(act).to.throw(expectedError);
});
describe('returns existing function', () => {
it('when function with inline code is added', () => {
// arrange
const expected = new SharedFunctionStub(FunctionBodyType.Code)
.withName('expected-function-name');
const sut = new SharedFunctionCollection();
// act
sut.addFunction(expected);
const actual = sut.getFunctionByName(expected.name);
// assert
expect(actual).to.equal(expected);
});
it('when calling function is added', () => {
// arrange
const callee = new SharedFunctionStub(FunctionBodyType.Code)
.withName('calleeFunction');
const caller = new SharedFunctionStub(FunctionBodyType.Calls)
.withName('callerFunction')
.withCalls(new FunctionCallStub().withFunctionName(callee.name));
const sut = new SharedFunctionCollection();
// act
sut.addFunction(callee);
sut.addFunction(caller);
const actual = sut.getFunctionByName(caller.name);
// assert
expect(actual).to.equal(caller);
});
});
});
});

View File

@@ -1,7 +1,7 @@
import 'mocha';
import { expect } from 'chai';
import { ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
import { FunctionData } from 'js-yaml-loader!@/*';
import { ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
import { SharedFunctionsParser } from '@/application/Parser/Script/Compiler/Function/SharedFunctionsParser';
import { FunctionDataStub } from '@tests/unit/stubs/FunctionDataStub';
import { ParameterDefinitionDataStub } from '@tests/unit/stubs/ParameterDefinitionDataStub';
@@ -9,269 +9,270 @@ import { FunctionParameter } from '@/application/Parser/Script/Compiler/Function
import { FunctionCallDataStub } from '@tests/unit/stubs/FunctionCallDataStub';
describe('SharedFunctionsParser', () => {
describe('parseFunctions', () => {
describe('validates functions', () => {
it('throws if one of the functions is undefined', () => {
// arrange
const expectedError = `some functions are undefined`;
const functions = [ FunctionDataStub.createWithCode(), undefined ];
const sut = new SharedFunctionsParser();
// act
const act = () => sut.parseFunctions(functions);
// assert
expect(act).to.throw(expectedError);
});
it('throws when functions have same names', () => {
// arrange
const name = 'same-func-name';
const expectedError = `duplicate function name: "${name}"`;
const functions = [
FunctionDataStub.createWithCode().withName(name),
FunctionDataStub.createWithCode().withName(name),
];
const sut = new SharedFunctionsParser();
// act
const act = () => sut.parseFunctions(functions);
// assert
expect(act).to.throw(expectedError);
});
describe('throws when when function have duplicate code', () => {
it('code', () => {
// arrange
const code = 'duplicate-code';
const expectedError = `duplicate "code" in functions: "${code}"`;
const functions = [
FunctionDataStub.createWithoutCallOrCodes().withName('func-1').withCode(code),
FunctionDataStub.createWithoutCallOrCodes().withName('func-2').withCode(code),
];
const sut = new SharedFunctionsParser();
// act
const act = () => sut.parseFunctions(functions);
// assert
expect(act).to.throw(expectedError);
});
it('revertCode', () => {
// arrange
const revertCode = 'duplicate-revert-code';
const expectedError = `duplicate "revertCode" in functions: "${revertCode}"`;
const functions = [
FunctionDataStub.createWithoutCallOrCodes()
.withName('func-1').withCode('code-1').withRevertCode(revertCode),
FunctionDataStub.createWithoutCallOrCodes()
.withName('func-2').withCode('code-2').withRevertCode(revertCode),
];
const sut = new SharedFunctionsParser();
// act
const act = () => sut.parseFunctions(functions);
// assert
expect(act).to.throw(expectedError);
});
});
describe('ensures either call or code is defined', () => {
it('both code and call are defined', () => {
// arrange
const functionName = 'invalid-function';
const expectedError = `both "code" and "call" are defined in "${functionName}"`;
const invalidFunction = FunctionDataStub.createWithoutCallOrCodes()
.withName(functionName)
.withCode('code')
.withMockCall();
const sut = new SharedFunctionsParser();
// act
const act = () => sut.parseFunctions([ invalidFunction ]);
// assert
expect(act).to.throw(expectedError);
});
it('neither code and call is defined', () => {
// arrange
const functionName = 'invalid-function';
const expectedError = `neither "code" or "call" is defined in "${functionName}"`;
const invalidFunction = FunctionDataStub.createWithoutCallOrCodes()
.withName(functionName);
const sut = new SharedFunctionsParser();
// act
const act = () => sut.parseFunctions([ invalidFunction ]);
// 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 SharedFunctionsParser();
// act
const act = () => sut.parseFunctions([ func ]);
// 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;
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));
describe('parseFunctions', () => {
describe('validates functions', () => {
it('throws if one of the functions is undefined', () => {
// arrange
const expectedError = 'some functions are undefined';
const functions = [FunctionDataStub.createWithCode(), undefined];
const sut = new SharedFunctionsParser();
// act
const act = () => sut.parseFunctions(functions);
// assert
expect(act).to.throw(expectedError);
});
it('throws when functions have same names', () => {
// arrange
const name = 'same-func-name';
const expectedError = `duplicate function name: "${name}"`;
const functions = [
FunctionDataStub.createWithCode().withName(name),
FunctionDataStub.createWithCode().withName(name),
];
const sut = new SharedFunctionsParser();
// act
const act = () => sut.parseFunctions(functions);
// assert
expect(act).to.throw(expectedError);
});
describe('throws when when function have duplicate code', () => {
it('code', () => {
// arrange
const code = 'duplicate-code';
const expectedError = `duplicate "code" in functions: "${code}"`;
const functions = [
FunctionDataStub.createWithoutCallOrCodes().withName('func-1').withCode(code),
FunctionDataStub.createWithoutCallOrCodes().withName('func-2').withCode(code),
];
const sut = new SharedFunctionsParser();
// act
const act = () => sut.parseFunctions(functions);
// assert
expect(act).to.throw(expectedError);
});
it('revertCode', () => {
// arrange
const revertCode = 'duplicate-revert-code';
const expectedError = `duplicate "revertCode" in functions: "${revertCode}"`;
const functions = [
FunctionDataStub.createWithoutCallOrCodes()
.withName('func-1').withCode('code-1').withRevertCode(revertCode),
FunctionDataStub.createWithoutCallOrCodes()
.withName('func-2').withCode('code-2').withRevertCode(revertCode),
];
const sut = new SharedFunctionsParser();
// act
const act = () => sut.parseFunctions(functions);
// assert
expect(act).to.throw(expectedError);
});
});
describe('ensures either call or code is defined', () => {
it('both code and call are defined', () => {
// arrange
const functionName = 'invalid-function';
const expectedError = `both "code" and "call" are defined in "${functionName}"`;
const invalidFunction = FunctionDataStub.createWithoutCallOrCodes()
.withName(functionName)
.withCode('code')
.withMockCall();
const sut = new SharedFunctionsParser();
// act
const act = () => sut.parseFunctions([invalidFunction]);
// assert
expect(act).to.throw(expectedError);
});
it('neither code and call is defined', () => {
// arrange
const functionName = 'invalid-function';
const expectedError = `neither "code" or "call" is defined in "${functionName}"`;
const invalidFunction = FunctionDataStub.createWithoutCallOrCodes()
.withName(functionName);
const sut = new SharedFunctionsParser();
// act
const act = () => sut.parseFunctions([invalidFunction]);
// 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 never);
const expectedError = `parameters must be an array of objects in function(s) "${func.name}"`;
const sut = new SharedFunctionsParser();
// act
const act = () => sut.parseFunctions([func]);
// 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;
try {
// eslint-disable-next-line no-new
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 SharedFunctionsParser();
const act = () => sut.parseFunctions([ functionData ]);
// act
const sut = new SharedFunctionsParser();
const act = () => sut.parseFunctions([functionData]);
// assert
expect(act).to.throw(expectedError);
});
});
describe('empty functions', () => {
it('returns empty collection', () => {
// arrange
const emptyValues = [ [], undefined ];
const sut = new SharedFunctionsParser();
for (const emptyFunctions of emptyValues) {
// act
const actual = sut.parseFunctions(emptyFunctions);
// assert
expect(actual).to.not.equal(undefined);
}
});
});
describe('function with inline code', () => {
it('parses single function with code as expected', () => {
// arrange
const name = 'function-name';
const expected = FunctionDataStub
.createWithoutCallOrCodes()
.withName(name)
.withCode('expected-code')
.withRevertCode('expected-revert-code')
.withParameters(
new ParameterDefinitionDataStub().withName('expectedParameter').withOptionality(true),
new ParameterDefinitionDataStub().withName('expectedParameter2').withOptionality(false),
);
const sut = new SharedFunctionsParser();
// act
const collection = sut.parseFunctions([ expected ]);
// expect
const actual = collection.getFunctionByName(name);
expectEqualName(expected, actual);
expectEqualParameters(expected, actual);
expectEqualFunctionWithInlineCode(expected, actual);
});
});
describe('function with calls', () => {
it('parses single function with call as expected', () => {
// arrange
const call = new FunctionCallDataStub()
.withName('calleeFunction')
.withParameters({test: 'value'});
const data = FunctionDataStub.createWithoutCallOrCodes()
.withName('caller-function')
.withCall(call);
const sut = new SharedFunctionsParser();
// act
const collection = sut.parseFunctions([ data ]);
// expect
const actual = collection.getFunctionByName(data.name);
expectEqualName(data, actual);
expectEqualParameters(data, actual);
expectEqualCalls([ call ], actual);
});
it('parses multiple functions with call as expected', () => {
// arrange
const call1 = new FunctionCallDataStub()
.withName('calleeFunction1')
.withParameters({ param: 'value' });
const call2 = new FunctionCallDataStub()
.withName('calleeFunction2')
.withParameters( {param2: 'value2'});
const caller1 = FunctionDataStub.createWithoutCallOrCodes()
.withName('caller-function')
.withCall(call1);
const caller2 = FunctionDataStub.createWithoutCallOrCodes()
.withName('caller-function-2')
.withCall([ call1, call2 ]);
const sut = new SharedFunctionsParser();
// act
const collection = sut.parseFunctions([ caller1, caller2 ]);
// expect
const compiledCaller1 = collection.getFunctionByName(caller1.name);
expectEqualName(caller1, compiledCaller1);
expectEqualParameters(caller1, compiledCaller1);
expectEqualCalls([ call1 ], compiledCaller1);
const compiledCaller2 = collection.getFunctionByName(caller2.name);
expectEqualName(caller2, compiledCaller2);
expectEqualParameters(caller2, compiledCaller2);
expectEqualCalls([ call1, call2 ], compiledCaller2);
});
});
// assert
expect(act).to.throw(expectedError);
});
});
describe('empty functions', () => {
it('returns empty collection', () => {
// arrange
const emptyValues = [[], undefined];
const sut = new SharedFunctionsParser();
for (const emptyFunctions of emptyValues) {
// act
const actual = sut.parseFunctions(emptyFunctions);
// assert
expect(actual).to.not.equal(undefined);
}
});
});
describe('function with inline code', () => {
it('parses single function with code as expected', () => {
// arrange
const name = 'function-name';
const expected = FunctionDataStub
.createWithoutCallOrCodes()
.withName(name)
.withCode('expected-code')
.withRevertCode('expected-revert-code')
.withParameters(
new ParameterDefinitionDataStub().withName('expectedParameter').withOptionality(true),
new ParameterDefinitionDataStub().withName('expectedParameter2').withOptionality(false),
);
const sut = new SharedFunctionsParser();
// act
const collection = sut.parseFunctions([expected]);
// expect
const actual = collection.getFunctionByName(name);
expectEqualName(expected, actual);
expectEqualParameters(expected, actual);
expectEqualFunctionWithInlineCode(expected, actual);
});
});
describe('function with calls', () => {
it('parses single function with call as expected', () => {
// arrange
const call = new FunctionCallDataStub()
.withName('calleeFunction')
.withParameters({ test: 'value' });
const data = FunctionDataStub.createWithoutCallOrCodes()
.withName('caller-function')
.withCall(call);
const sut = new SharedFunctionsParser();
// act
const collection = sut.parseFunctions([data]);
// expect
const actual = collection.getFunctionByName(data.name);
expectEqualName(data, actual);
expectEqualParameters(data, actual);
expectEqualCalls([call], actual);
});
it('parses multiple functions with call as expected', () => {
// arrange
const call1 = new FunctionCallDataStub()
.withName('calleeFunction1')
.withParameters({ param: 'value' });
const call2 = new FunctionCallDataStub()
.withName('calleeFunction2')
.withParameters({ param2: 'value2' });
const caller1 = FunctionDataStub.createWithoutCallOrCodes()
.withName('caller-function')
.withCall(call1);
const caller2 = FunctionDataStub.createWithoutCallOrCodes()
.withName('caller-function-2')
.withCall([call1, call2]);
const sut = new SharedFunctionsParser();
// act
const collection = sut.parseFunctions([caller1, caller2]);
// expect
const compiledCaller1 = collection.getFunctionByName(caller1.name);
expectEqualName(caller1, compiledCaller1);
expectEqualParameters(caller1, compiledCaller1);
expectEqualCalls([call1], compiledCaller1);
const compiledCaller2 = collection.getFunctionByName(caller2.name);
expectEqualName(caller2, compiledCaller2);
expectEqualParameters(caller2, compiledCaller2);
expectEqualCalls([call1, call2], compiledCaller2);
});
});
});
});
function expectEqualName(
expected: FunctionDataStub, actual: ISharedFunction): void {
expect(actual.name).to.equal(expected.name);
function expectEqualName(expected: FunctionDataStub, actual: ISharedFunction): void {
expect(actual.name).to.equal(expected.name);
}
function expectEqualParameters(
expected: FunctionDataStub, actual: ISharedFunction): void {
const actualSimplifiedParameters = actual.parameters.all.map((parameter) => ({
name: parameter.name,
optional: parameter.isOptional,
}));
const expectedSimplifiedParameters = expected.parameters?.map((parameter) => ({
name: parameter.name,
optional: parameter.optional || false,
})) || [];
expect(expectedSimplifiedParameters).to.deep.equal(actualSimplifiedParameters, 'Unequal parameters');
function expectEqualParameters(expected: FunctionDataStub, actual: ISharedFunction): void {
const actualSimplifiedParameters = actual.parameters.all.map((parameter) => ({
name: parameter.name,
optional: parameter.isOptional,
}));
const expectedSimplifiedParameters = expected.parameters?.map((parameter) => ({
name: parameter.name,
optional: parameter.optional || false,
})) || [];
expect(expectedSimplifiedParameters).to.deep.equal(actualSimplifiedParameters, 'Unequal parameters');
}
function expectEqualFunctionWithInlineCode(
expected: FunctionData, actual: ISharedFunction): void {
expect(actual.body,
`function "${actual.name}" has no body`);
expect(actual.body.code,
`function "${actual.name}" has no code`);
expect(actual.body.code.do).to.equal(expected.code);
expect(actual.body.code.revert).to.equal(expected.revertCode);
expected: FunctionData,
actual: ISharedFunction,
): void {
expect(actual.body, `function "${actual.name}" has no body`);
expect(actual.body.code, `function "${actual.name}" has no code`);
expect(actual.body.code.do).to.equal(expected.code);
expect(actual.body.code.revert).to.equal(expected.revertCode);
}
function expectEqualCalls(
expected: FunctionCallDataStub[], actual: ISharedFunction) {
expect(actual.body,
`function "${actual.name}" has no body`);
expect(actual.body.calls,
`function "${actual.name}" has no calls`);
const actualSimplifiedCalls = actual.body.calls
.map((call) => ({
function: call.functionName,
params: call.args.getAllParameterNames().map((name) => ({
name, value: call.args.getArgument(name).argumentValue,
})),
}));
const expectedSimplifiedCalls = expected
.map((call) => ({
function: call.function,
params: Object.keys(call.parameters).map((key) => (
{ name: key, value: call.parameters[key] }
)),
}));
expect(actualSimplifiedCalls).to.deep.equal(expectedSimplifiedCalls, 'Unequal calls');
expected: FunctionCallDataStub[],
actual: ISharedFunction,
) {
expect(actual.body, `function "${actual.name}" has no body`);
expect(actual.body.calls, `function "${actual.name}" has no calls`);
const actualSimplifiedCalls = actual.body.calls
.map((call) => ({
function: call.functionName,
params: call.args.getAllParameterNames().map((name) => ({
name, value: call.args.getArgument(name).argumentValue,
})),
}));
const expectedSimplifiedCalls = expected
.map((call) => ({
function: call.function,
params: Object.keys(call.parameters).map((key) => (
{ name: key, value: call.parameters[key] }
)),
}));
expect(actualSimplifiedCalls).to.deep.equal(expectedSimplifiedCalls, 'Unequal calls');
}