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:
@@ -11,72 +11,72 @@ import { ScriptingDefinitionStub } from '@tests/unit/stubs/ScriptingDefinitionSt
|
||||
import { FunctionDataStub } from '@tests/unit/stubs/FunctionDataStub';
|
||||
|
||||
describe('CategoryCollectionParseContext', () => {
|
||||
describe('ctor', () => {
|
||||
describe('functionsData', () => {
|
||||
it('can create with empty values', () => {
|
||||
// arrange
|
||||
const testData: FunctionData[][] = [ undefined, [] ];
|
||||
const scripting = new ScriptingDefinitionStub();
|
||||
for (const functionsData of testData) {
|
||||
// act
|
||||
const act = () => new CategoryCollectionParseContext(functionsData, scripting);
|
||||
// assert
|
||||
expect(act).to.not.throw();
|
||||
}
|
||||
});
|
||||
});
|
||||
it('scripting', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined scripting';
|
||||
const scripting = undefined;
|
||||
const functionsData = [ FunctionDataStub.createWithCode() ];
|
||||
// act
|
||||
const act = () => new CategoryCollectionParseContext(functionsData, scripting);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('ctor', () => {
|
||||
describe('functionsData', () => {
|
||||
it('can create with empty values', () => {
|
||||
// arrange
|
||||
const testData: FunctionData[][] = [undefined, []];
|
||||
const scripting = new ScriptingDefinitionStub();
|
||||
for (const functionsData of testData) {
|
||||
// act
|
||||
const act = () => new CategoryCollectionParseContext(functionsData, scripting);
|
||||
// assert
|
||||
expect(act).to.not.throw();
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('compiler', () => {
|
||||
it('constructed as expected', () => {
|
||||
// arrange
|
||||
const functionsData = [ FunctionDataStub.createWithCode() ];
|
||||
const syntax = new LanguageSyntaxStub();
|
||||
const expected = new ScriptCompiler(functionsData, syntax);
|
||||
const language = ScriptingLanguage.shellscript;
|
||||
const factoryMock = mockFactory(language, syntax);
|
||||
const definition = new ScriptingDefinitionStub()
|
||||
.withLanguage(language);
|
||||
// act
|
||||
const sut = new CategoryCollectionParseContext(functionsData, definition, factoryMock);
|
||||
const actual = sut.compiler;
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
it('scripting', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined scripting';
|
||||
const scripting = undefined;
|
||||
const functionsData = [FunctionDataStub.createWithCode()];
|
||||
// act
|
||||
const act = () => new CategoryCollectionParseContext(functionsData, scripting);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('syntax', () => {
|
||||
it('set from syntax factory', () => {
|
||||
// arrange
|
||||
const language = ScriptingLanguage.shellscript;
|
||||
const expected = new LanguageSyntaxStub();
|
||||
const factoryMock = mockFactory(language, expected);
|
||||
const definition = new ScriptingDefinitionStub()
|
||||
.withLanguage(language);
|
||||
// act
|
||||
const sut = new CategoryCollectionParseContext([], definition, factoryMock);
|
||||
const actual = sut.syntax;
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('compiler', () => {
|
||||
it('constructed as expected', () => {
|
||||
// arrange
|
||||
const functionsData = [FunctionDataStub.createWithCode()];
|
||||
const syntax = new LanguageSyntaxStub();
|
||||
const expected = new ScriptCompiler(functionsData, syntax);
|
||||
const language = ScriptingLanguage.shellscript;
|
||||
const factoryMock = mockFactory(language, syntax);
|
||||
const definition = new ScriptingDefinitionStub()
|
||||
.withLanguage(language);
|
||||
// act
|
||||
const sut = new CategoryCollectionParseContext(functionsData, definition, factoryMock);
|
||||
const actual = sut.compiler;
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('syntax', () => {
|
||||
it('set from syntax factory', () => {
|
||||
// arrange
|
||||
const language = ScriptingLanguage.shellscript;
|
||||
const expected = new LanguageSyntaxStub();
|
||||
const factoryMock = mockFactory(language, expected);
|
||||
const definition = new ScriptingDefinitionStub()
|
||||
.withLanguage(language);
|
||||
// act
|
||||
const sut = new CategoryCollectionParseContext([], definition, factoryMock);
|
||||
const actual = sut.syntax;
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function mockFactory(expectedLanguage: ScriptingLanguage, result: ILanguageSyntax): ISyntaxFactory {
|
||||
return {
|
||||
create: (language: ScriptingLanguage) => {
|
||||
if (language !== expectedLanguage) {
|
||||
throw new Error('unexpected language');
|
||||
}
|
||||
return result;
|
||||
},
|
||||
};
|
||||
return {
|
||||
create: (language: ScriptingLanguage) => {
|
||||
if (language !== expectedLanguage) {
|
||||
throw new Error('unexpected language');
|
||||
}
|
||||
return result;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12,235 +12,242 @@ import { PipelineCompilerStub } from '@tests/unit/stubs/PipelineCompilerStub';
|
||||
import { IReadOnlyFunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/IFunctionParameterCollection';
|
||||
|
||||
describe('Expression', () => {
|
||||
describe('ctor', () => {
|
||||
describe('position', () => {
|
||||
it('throws if undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined position';
|
||||
const position = undefined;
|
||||
// act
|
||||
const act = () => new ExpressionBuilder()
|
||||
.withPosition(position)
|
||||
.build();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
const expected = new ExpressionPosition(0, 5);
|
||||
// act
|
||||
const actual = new ExpressionBuilder()
|
||||
.withPosition(expected)
|
||||
.build();
|
||||
// assert
|
||||
expect(actual.position).to.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('parameters', () => {
|
||||
it('defaults to empty array if undefined', () => {
|
||||
// arrange
|
||||
const parameters = undefined;
|
||||
// act
|
||||
const actual = new ExpressionBuilder()
|
||||
.withParameters(parameters)
|
||||
.build();
|
||||
// assert
|
||||
expect(actual.parameters);
|
||||
expect(actual.parameters.all);
|
||||
expect(actual.parameters.all.length).to.equal(0);
|
||||
});
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
const expected = new FunctionParameterCollectionStub()
|
||||
.withParameterName('firstParameterName')
|
||||
.withParameterName('secondParameterName');
|
||||
// act
|
||||
const actual = new ExpressionBuilder()
|
||||
.withParameters(expected)
|
||||
.build();
|
||||
// assert
|
||||
expect(actual.parameters).to.deep.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('evaluator', () => {
|
||||
it('throws if undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined evaluator';
|
||||
const evaluator = undefined;
|
||||
// act
|
||||
const act = () => new ExpressionBuilder()
|
||||
.withEvaluator(evaluator)
|
||||
.build();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('ctor', () => {
|
||||
describe('position', () => {
|
||||
it('throws if undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined position';
|
||||
const position = undefined;
|
||||
// act
|
||||
const act = () => new ExpressionBuilder()
|
||||
.withPosition(position)
|
||||
.build();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
const expected = new ExpressionPosition(0, 5);
|
||||
// act
|
||||
const actual = new ExpressionBuilder()
|
||||
.withPosition(expected)
|
||||
.build();
|
||||
// assert
|
||||
expect(actual.position).to.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('evaluate', () => {
|
||||
describe('throws with invalid arguments', () => {
|
||||
const testCases = [
|
||||
{
|
||||
name: 'throws if arguments is undefined',
|
||||
context: undefined,
|
||||
expectedError: 'undefined context',
|
||||
},
|
||||
{
|
||||
name: 'throws when some of the required args are not provided',
|
||||
sut: (i: ExpressionBuilder) => i.withParameterNames(['a', 'b', 'c'], false),
|
||||
context: new ExpressionEvaluationContextStub()
|
||||
.withArgs(new FunctionCallArgumentCollectionStub().withArgument('b', 'provided')),
|
||||
expectedError: 'argument values are provided for required parameters: "a", "c"',
|
||||
},
|
||||
{
|
||||
name: 'throws when none of the required args are not provided',
|
||||
sut: (i: ExpressionBuilder) => i.withParameterNames(['a', 'b'], false),
|
||||
context: new ExpressionEvaluationContextStub()
|
||||
.withArgs(new FunctionCallArgumentCollectionStub().withArgument('c', 'unrelated')),
|
||||
expectedError: 'argument values are provided for required parameters: "a", "b"',
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// arrange
|
||||
const sutBuilder = new ExpressionBuilder();
|
||||
if (testCase.sut) {
|
||||
testCase.sut(sutBuilder);
|
||||
}
|
||||
const sut = sutBuilder.build();
|
||||
// act
|
||||
const act = () => sut.evaluate(testCase.context);
|
||||
// assert
|
||||
expect(act).to.throw(testCase.expectedError);
|
||||
});
|
||||
}
|
||||
});
|
||||
it('returns result from evaluator', () => {
|
||||
// arrange
|
||||
const evaluatorMock: ExpressionEvaluator = (c) =>
|
||||
`"${c
|
||||
.args
|
||||
.getAllParameterNames()
|
||||
.map((name) => context.args.getArgument(name))
|
||||
.map((arg) => `${arg.parameterName}': '${arg.argumentValue}'`)
|
||||
.join('", "')}"`;
|
||||
const givenArguments = new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('parameter1', 'value1')
|
||||
.withArgument('parameter2', 'value2');
|
||||
const expectedParameterNames = givenArguments.getAllParameterNames();
|
||||
const context = new ExpressionEvaluationContextStub()
|
||||
.withArgs(givenArguments);
|
||||
const expected = evaluatorMock(context);
|
||||
const sut = new ExpressionBuilder()
|
||||
.withEvaluator(evaluatorMock)
|
||||
.withParameterNames(expectedParameterNames)
|
||||
.build();
|
||||
// arrange
|
||||
const actual = sut.evaluate(context);
|
||||
// assert
|
||||
expect(expected).to.equal(actual,
|
||||
`\nGiven arguments: ${JSON.stringify(givenArguments)}\n` +
|
||||
`\nExpected parameter names: ${JSON.stringify(expectedParameterNames)}\n`,
|
||||
);
|
||||
});
|
||||
it('sends pipeline compiler as it is', () => {
|
||||
// arrange
|
||||
const expected = new PipelineCompilerStub();
|
||||
const context = new ExpressionEvaluationContextStub()
|
||||
.withPipelineCompiler(expected);
|
||||
let actual: IPipelineCompiler;
|
||||
const evaluatorMock: ExpressionEvaluator = (c) => {
|
||||
actual = c.pipelineCompiler;
|
||||
return '';
|
||||
};
|
||||
const sut = new ExpressionBuilder()
|
||||
.withEvaluator(evaluatorMock)
|
||||
.build();
|
||||
// arrange
|
||||
sut.evaluate(context);
|
||||
// assert
|
||||
expect(expected).to.equal(actual);
|
||||
});
|
||||
describe('filters unused parameters', () => {
|
||||
// arrange
|
||||
const testCases = [
|
||||
{
|
||||
name: 'with a provided argument',
|
||||
expressionParameters: new FunctionParameterCollectionStub()
|
||||
.withParameterName('parameterToHave', false),
|
||||
arguments: new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('parameterToHave', 'value-to-have')
|
||||
.withArgument('parameterToIgnore', 'value-to-ignore'),
|
||||
expectedArguments: [
|
||||
new FunctionCallArgumentStub()
|
||||
.withParameterName('parameterToHave').withArgumentValue('value-to-have'),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'without a provided argument',
|
||||
expressionParameters: new FunctionParameterCollectionStub()
|
||||
.withParameterName('parameterToHave', false)
|
||||
.withParameterName('parameterToIgnore', true),
|
||||
arguments: new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('parameterToHave', 'value-to-have'),
|
||||
expectedArguments: [
|
||||
new FunctionCallArgumentStub()
|
||||
.withParameterName('parameterToHave').withArgumentValue('value-to-have'),
|
||||
],
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
let actual: IReadOnlyFunctionCallArgumentCollection;
|
||||
const evaluatorMock: ExpressionEvaluator = (c) => {
|
||||
actual = c.args;
|
||||
return '';
|
||||
};
|
||||
const context = new ExpressionEvaluationContextStub()
|
||||
.withArgs(testCase.arguments);
|
||||
const sut = new ExpressionBuilder()
|
||||
.withEvaluator(evaluatorMock)
|
||||
.withParameters(testCase.expressionParameters)
|
||||
.build();
|
||||
// act
|
||||
sut.evaluate(context);
|
||||
// assert
|
||||
const actualArguments = actual.getAllParameterNames().map((name) => actual.getArgument(name));
|
||||
expect(actualArguments).to.deep.equal(testCase.expectedArguments);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('parameters', () => {
|
||||
it('defaults to empty array if undefined', () => {
|
||||
// arrange
|
||||
const parameters = undefined;
|
||||
// act
|
||||
const actual = new ExpressionBuilder()
|
||||
.withParameters(parameters)
|
||||
.build();
|
||||
// assert
|
||||
expect(actual.parameters);
|
||||
expect(actual.parameters.all);
|
||||
expect(actual.parameters.all.length).to.equal(0);
|
||||
});
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
const expected = new FunctionParameterCollectionStub()
|
||||
.withParameterName('firstParameterName')
|
||||
.withParameterName('secondParameterName');
|
||||
// act
|
||||
const actual = new ExpressionBuilder()
|
||||
.withParameters(expected)
|
||||
.build();
|
||||
// assert
|
||||
expect(actual.parameters).to.deep.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('evaluator', () => {
|
||||
it('throws if undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined evaluator';
|
||||
const evaluator = undefined;
|
||||
// act
|
||||
const act = () => new ExpressionBuilder()
|
||||
.withEvaluator(evaluator)
|
||||
.build();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('evaluate', () => {
|
||||
describe('throws with invalid arguments', () => {
|
||||
const testCases = [
|
||||
{
|
||||
name: 'throws if arguments is undefined',
|
||||
context: undefined,
|
||||
expectedError: 'undefined context',
|
||||
},
|
||||
{
|
||||
name: 'throws when some of the required args are not provided',
|
||||
sut: (i: ExpressionBuilder) => i.withParameterNames(['a', 'b', 'c'], false),
|
||||
context: new ExpressionEvaluationContextStub()
|
||||
.withArgs(new FunctionCallArgumentCollectionStub().withArgument('b', 'provided')),
|
||||
expectedError: 'argument values are provided for required parameters: "a", "c"',
|
||||
},
|
||||
{
|
||||
name: 'throws when none of the required args are not provided',
|
||||
sut: (i: ExpressionBuilder) => i.withParameterNames(['a', 'b'], false),
|
||||
context: new ExpressionEvaluationContextStub()
|
||||
.withArgs(new FunctionCallArgumentCollectionStub().withArgument('c', 'unrelated')),
|
||||
expectedError: 'argument values are provided for required parameters: "a", "b"',
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// arrange
|
||||
const sutBuilder = new ExpressionBuilder();
|
||||
if (testCase.sut) {
|
||||
testCase.sut(sutBuilder);
|
||||
}
|
||||
const sut = sutBuilder.build();
|
||||
// act
|
||||
const act = () => sut.evaluate(testCase.context);
|
||||
// assert
|
||||
expect(act).to.throw(testCase.expectedError);
|
||||
});
|
||||
}
|
||||
});
|
||||
it('returns result from evaluator', () => {
|
||||
// arrange
|
||||
const evaluatorMock: ExpressionEvaluator = (c) => `"${c
|
||||
.args
|
||||
.getAllParameterNames()
|
||||
.map((name) => context.args.getArgument(name))
|
||||
.map((arg) => `${arg.parameterName}': '${arg.argumentValue}'`)
|
||||
.join('", "')}"`;
|
||||
const givenArguments = new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('parameter1', 'value1')
|
||||
.withArgument('parameter2', 'value2');
|
||||
const expectedParameterNames = givenArguments.getAllParameterNames();
|
||||
const context = new ExpressionEvaluationContextStub()
|
||||
.withArgs(givenArguments);
|
||||
const expected = evaluatorMock(context);
|
||||
const sut = new ExpressionBuilder()
|
||||
.withEvaluator(evaluatorMock)
|
||||
.withParameterNames(expectedParameterNames)
|
||||
.build();
|
||||
// arrange
|
||||
const actual = sut.evaluate(context);
|
||||
// assert
|
||||
expect(expected).to.equal(actual, printMessage());
|
||||
function printMessage(): string {
|
||||
return `\nGiven arguments: ${JSON.stringify(givenArguments)}\n`
|
||||
+ `\nExpected parameter names: ${JSON.stringify(expectedParameterNames)}\n`;
|
||||
}
|
||||
});
|
||||
it('sends pipeline compiler as it is', () => {
|
||||
// arrange
|
||||
const expected = new PipelineCompilerStub();
|
||||
const context = new ExpressionEvaluationContextStub()
|
||||
.withPipelineCompiler(expected);
|
||||
let actual: IPipelineCompiler;
|
||||
const evaluatorMock: ExpressionEvaluator = (c) => {
|
||||
actual = c.pipelineCompiler;
|
||||
return '';
|
||||
};
|
||||
const sut = new ExpressionBuilder()
|
||||
.withEvaluator(evaluatorMock)
|
||||
.build();
|
||||
// arrange
|
||||
sut.evaluate(context);
|
||||
// assert
|
||||
expect(expected).to.equal(actual);
|
||||
});
|
||||
describe('filters unused parameters', () => {
|
||||
// arrange
|
||||
const testCases = [
|
||||
{
|
||||
name: 'with a provided argument',
|
||||
expressionParameters: new FunctionParameterCollectionStub()
|
||||
.withParameterName('parameterToHave', false),
|
||||
arguments: new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('parameterToHave', 'value-to-have')
|
||||
.withArgument('parameterToIgnore', 'value-to-ignore'),
|
||||
expectedArguments: [
|
||||
new FunctionCallArgumentStub()
|
||||
.withParameterName('parameterToHave').withArgumentValue('value-to-have'),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'without a provided argument',
|
||||
expressionParameters: new FunctionParameterCollectionStub()
|
||||
.withParameterName('parameterToHave', false)
|
||||
.withParameterName('parameterToIgnore', true),
|
||||
arguments: new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('parameterToHave', 'value-to-have'),
|
||||
expectedArguments: [
|
||||
new FunctionCallArgumentStub()
|
||||
.withParameterName('parameterToHave').withArgumentValue('value-to-have'),
|
||||
],
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
let actual: IReadOnlyFunctionCallArgumentCollection;
|
||||
const evaluatorMock: ExpressionEvaluator = (c) => {
|
||||
actual = c.args;
|
||||
return '';
|
||||
};
|
||||
const context = new ExpressionEvaluationContextStub()
|
||||
.withArgs(testCase.arguments);
|
||||
const sut = new ExpressionBuilder()
|
||||
.withEvaluator(evaluatorMock)
|
||||
.withParameters(testCase.expressionParameters)
|
||||
.build();
|
||||
// act
|
||||
sut.evaluate(context);
|
||||
// assert
|
||||
const actualArguments = actual.getAllParameterNames()
|
||||
.map((name) => actual.getArgument(name));
|
||||
expect(actualArguments).to.deep.equal(testCase.expectedArguments);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class ExpressionBuilder {
|
||||
private position: ExpressionPosition = new ExpressionPosition(0, 5);
|
||||
private parameters: IReadOnlyFunctionParameterCollection = new FunctionParameterCollectionStub();
|
||||
private position: ExpressionPosition = new ExpressionPosition(0, 5);
|
||||
|
||||
public withPosition(position: ExpressionPosition) {
|
||||
this.position = position;
|
||||
return this;
|
||||
}
|
||||
public withEvaluator(evaluator: ExpressionEvaluator) {
|
||||
this.evaluator = evaluator;
|
||||
return this;
|
||||
}
|
||||
public withParameters(parameters: IReadOnlyFunctionParameterCollection) {
|
||||
this.parameters = parameters;
|
||||
return this;
|
||||
}
|
||||
public withParameterName(parameterName: string, isOptional: boolean = true) {
|
||||
const collection = new FunctionParameterCollectionStub()
|
||||
.withParameterName(parameterName, isOptional);
|
||||
return this.withParameters(collection);
|
||||
}
|
||||
public withParameterNames(parameterNames: string[], isOptional: boolean = true) {
|
||||
const collection = new FunctionParameterCollectionStub()
|
||||
.withParameterNames(parameterNames, isOptional);
|
||||
return this.withParameters(collection);
|
||||
}
|
||||
public build() {
|
||||
return new Expression(this.position, this.evaluator, this.parameters);
|
||||
}
|
||||
private parameters: IReadOnlyFunctionParameterCollection = new FunctionParameterCollectionStub();
|
||||
|
||||
private evaluator: ExpressionEvaluator = () => '';
|
||||
public withPosition(position: ExpressionPosition) {
|
||||
this.position = position;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withEvaluator(evaluator: ExpressionEvaluator) {
|
||||
this.evaluator = evaluator;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withParameters(parameters: IReadOnlyFunctionParameterCollection) {
|
||||
this.parameters = parameters;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withParameterName(parameterName: string, isOptional = true) {
|
||||
const collection = new FunctionParameterCollectionStub()
|
||||
.withParameterName(parameterName, isOptional);
|
||||
return this.withParameters(collection);
|
||||
}
|
||||
|
||||
public withParameterNames(parameterNames: string[], isOptional = true) {
|
||||
const collection = new FunctionParameterCollectionStub()
|
||||
.withParameterNames(parameterNames, isOptional);
|
||||
return this.withParameters(collection);
|
||||
}
|
||||
|
||||
public build() {
|
||||
return new Expression(this.position, this.evaluator, this.parameters);
|
||||
}
|
||||
|
||||
private evaluator: ExpressionEvaluator = () => '';
|
||||
}
|
||||
|
||||
@@ -6,60 +6,63 @@ import { IPipelineCompiler } from '@/application/Parser/Script/Compiler/Expressi
|
||||
import { FunctionCallArgumentCollectionStub } from '@tests/unit/stubs/FunctionCallArgumentCollectionStub';
|
||||
import { PipelineCompilerStub } from '@tests/unit/stubs/PipelineCompilerStub';
|
||||
|
||||
|
||||
describe('ExpressionEvaluationContext', () => {
|
||||
describe('ctor', () => {
|
||||
describe('args', () => {
|
||||
it('throws if args are undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined args';
|
||||
const builder = new ExpressionEvaluationContextBuilder()
|
||||
.withArgs(undefined);
|
||||
// act
|
||||
const act = () => builder.build();
|
||||
// assert
|
||||
expect(act).throw(expectedError);
|
||||
});
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
const expected = new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('expectedParameter', 'expectedValue');
|
||||
const builder = new ExpressionEvaluationContextBuilder()
|
||||
.withArgs(expected);
|
||||
// act
|
||||
const sut = builder.build();
|
||||
// assert
|
||||
const actual = sut.args;
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('pipelineCompiler', () => {
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
const expected = new PipelineCompilerStub();
|
||||
const builder = new ExpressionEvaluationContextBuilder()
|
||||
.withPipelineCompiler(expected);
|
||||
// act
|
||||
const sut = builder.build();
|
||||
// assert
|
||||
expect(sut.pipelineCompiler).to.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('ctor', () => {
|
||||
describe('args', () => {
|
||||
it('throws if args are undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined args';
|
||||
const builder = new ExpressionEvaluationContextBuilder()
|
||||
.withArgs(undefined);
|
||||
// act
|
||||
const act = () => builder.build();
|
||||
// assert
|
||||
expect(act).throw(expectedError);
|
||||
});
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
const expected = new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('expectedParameter', 'expectedValue');
|
||||
const builder = new ExpressionEvaluationContextBuilder()
|
||||
.withArgs(expected);
|
||||
// act
|
||||
const sut = builder.build();
|
||||
// assert
|
||||
const actual = sut.args;
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('pipelineCompiler', () => {
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
const expected = new PipelineCompilerStub();
|
||||
const builder = new ExpressionEvaluationContextBuilder()
|
||||
.withPipelineCompiler(expected);
|
||||
// act
|
||||
const sut = builder.build();
|
||||
// assert
|
||||
expect(sut.pipelineCompiler).to.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class ExpressionEvaluationContextBuilder {
|
||||
private args: IReadOnlyFunctionCallArgumentCollection = new FunctionCallArgumentCollectionStub();
|
||||
private pipelineCompiler: IPipelineCompiler = new PipelineCompilerStub();
|
||||
public withArgs(args: IReadOnlyFunctionCallArgumentCollection) {
|
||||
this.args = args;
|
||||
return this;
|
||||
}
|
||||
public withPipelineCompiler(pipelineCompiler: IPipelineCompiler) {
|
||||
this.pipelineCompiler = pipelineCompiler;
|
||||
return this;
|
||||
}
|
||||
public build(): IExpressionEvaluationContext {
|
||||
return new ExpressionEvaluationContext(this.args, this.pipelineCompiler);
|
||||
}
|
||||
private args: IReadOnlyFunctionCallArgumentCollection = new FunctionCallArgumentCollectionStub();
|
||||
|
||||
private pipelineCompiler: IPipelineCompiler = new PipelineCompilerStub();
|
||||
|
||||
public withArgs(args: IReadOnlyFunctionCallArgumentCollection) {
|
||||
this.args = args;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withPipelineCompiler(pipelineCompiler: IPipelineCompiler) {
|
||||
this.pipelineCompiler = pipelineCompiler;
|
||||
return this;
|
||||
}
|
||||
|
||||
public build(): IExpressionEvaluationContext {
|
||||
return new ExpressionEvaluationContext(this.args, this.pipelineCompiler);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,32 +3,32 @@ import { expect } from 'chai';
|
||||
import { ExpressionPosition } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionPosition';
|
||||
|
||||
describe('ExpressionPosition', () => {
|
||||
describe('ctor', () => {
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
const expectedStart = 0;
|
||||
const expectedEnd = 5;
|
||||
// act
|
||||
const sut = new ExpressionPosition(expectedStart, expectedEnd);
|
||||
// assert
|
||||
expect(sut.start).to.equal(expectedStart);
|
||||
expect(sut.end).to.equal(expectedEnd);
|
||||
});
|
||||
describe('throws when invalid', () => {
|
||||
// arrange
|
||||
const testCases = [
|
||||
{ start: 5, end: 5, error: 'no length (start = end = 5)' },
|
||||
{ start: 5, end: 3, error: 'start (5) after end (3)' },
|
||||
{ start: -1, end: 3, error: 'negative start position: -1' },
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.error, () => {
|
||||
// act
|
||||
const act = () => new ExpressionPosition(testCase.start, testCase.end);
|
||||
// assert
|
||||
expect(act).to.throw(testCase.error);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('ctor', () => {
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
const expectedStart = 0;
|
||||
const expectedEnd = 5;
|
||||
// act
|
||||
const sut = new ExpressionPosition(expectedStart, expectedEnd);
|
||||
// assert
|
||||
expect(sut.start).to.equal(expectedStart);
|
||||
expect(sut.end).to.equal(expectedEnd);
|
||||
});
|
||||
describe('throws when invalid', () => {
|
||||
// arrange
|
||||
const testCases = [
|
||||
{ start: 5, end: 5, error: 'no length (start = end = 5)' },
|
||||
{ start: 5, end: 3, error: 'start (5) after end (3)' },
|
||||
{ start: -1, end: 3, error: 'negative start position: -1' },
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.error, () => {
|
||||
// act
|
||||
const act = () => new ExpressionPosition(testCase.start, testCase.end);
|
||||
// assert
|
||||
expect(act).to.throw(testCase.error);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,181 +7,181 @@ import { ExpressionParserStub } from '@tests/unit/stubs/ExpressionParserStub';
|
||||
import { FunctionCallArgumentCollectionStub } from '@tests/unit/stubs/FunctionCallArgumentCollectionStub';
|
||||
|
||||
describe('ExpressionsCompiler', () => {
|
||||
describe('compileExpressions', () => {
|
||||
describe('returns code when it is empty or undefined', () => {
|
||||
// arrange
|
||||
const testCases = [{
|
||||
name: 'empty',
|
||||
value: '',
|
||||
}, {
|
||||
name: 'undefined',
|
||||
value: undefined,
|
||||
},
|
||||
];
|
||||
for (const test of testCases) {
|
||||
it(`given ${test.name}`, () => {
|
||||
const expected = test.value;
|
||||
const sut = new SystemUnderTest();
|
||||
const args = new FunctionCallArgumentCollectionStub();
|
||||
// act
|
||||
const value = sut.compileExpressions(test.value, args);
|
||||
// assert
|
||||
expect(value).to.equal(expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('combines expressions as expected', () => {
|
||||
// arrange
|
||||
const code = 'part1 {{ a }} part2 {{ b }} part3';
|
||||
const testCases = [
|
||||
{
|
||||
name: 'with ordered expressions',
|
||||
expressions: [
|
||||
new ExpressionStub().withPosition(6, 13).withEvaluatedResult('a'),
|
||||
new ExpressionStub().withPosition(20, 27).withEvaluatedResult('b'),
|
||||
],
|
||||
expected: 'part1 a part2 b part3',
|
||||
},
|
||||
{
|
||||
name: 'unordered expressions',
|
||||
expressions: [
|
||||
new ExpressionStub().withPosition(20, 27).withEvaluatedResult('b'),
|
||||
new ExpressionStub().withPosition(6, 13).withEvaluatedResult('a'),
|
||||
],
|
||||
expected: 'part1 a part2 b part3',
|
||||
},
|
||||
{
|
||||
name: 'with an optional expected argument that is not provided',
|
||||
expressions: [
|
||||
new ExpressionStub().withPosition(6, 13).withEvaluatedResult('a')
|
||||
.withParameterNames(['optionalParameter'], true),
|
||||
new ExpressionStub().withPosition(20, 27).withEvaluatedResult('b')
|
||||
.withParameterNames(['optionalParameterTwo'], true),
|
||||
],
|
||||
expected: 'part1 a part2 b part3',
|
||||
},
|
||||
{
|
||||
name: 'with no expressions',
|
||||
expressions: [],
|
||||
expected: code,
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const expressionParserMock = new ExpressionParserStub()
|
||||
.withResult(testCase.expressions);
|
||||
const args = new FunctionCallArgumentCollectionStub();
|
||||
const sut = new SystemUnderTest(expressionParserMock);
|
||||
// act
|
||||
const actual = sut.compileExpressions(code, args);
|
||||
// assert
|
||||
expect(actual).to.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('arguments', () => {
|
||||
it('passes arguments to expressions as expected', () => {
|
||||
// arrange
|
||||
const expected = new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('test-arg', 'test-value');
|
||||
const code = 'non-important';
|
||||
const expressions = [
|
||||
new ExpressionStub(),
|
||||
new ExpressionStub(),
|
||||
];
|
||||
const expressionParserMock = new ExpressionParserStub()
|
||||
.withResult(expressions);
|
||||
const sut = new SystemUnderTest(expressionParserMock);
|
||||
// act
|
||||
sut.compileExpressions(code, expected);
|
||||
// assert
|
||||
expect(expressions[0].callHistory).to.have.lengthOf(1);
|
||||
expect(expressions[0].callHistory[0].args).to.equal(expected);
|
||||
expect(expressions[1].callHistory).to.have.lengthOf(1);
|
||||
expect(expressions[1].callHistory[0].args).to.equal(expected);
|
||||
});
|
||||
it('throws if arguments is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined args, send empty collection instead';
|
||||
const args = undefined;
|
||||
const expressionParserMock = new ExpressionParserStub();
|
||||
const sut = new SystemUnderTest(expressionParserMock);
|
||||
// act
|
||||
const act = () => sut.compileExpressions('code', args);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('throws when expected argument is not provided but used in code', () => {
|
||||
// arrange
|
||||
const testCases = [
|
||||
{
|
||||
name: 'empty parameters',
|
||||
expressions: [
|
||||
new ExpressionStub().withParameterNames(['parameter'], false),
|
||||
],
|
||||
args: new FunctionCallArgumentCollectionStub(),
|
||||
expectedError: 'parameter value(s) not provided for: "parameter" but used in code',
|
||||
},
|
||||
{
|
||||
name: 'unnecessary parameter is provided',
|
||||
expressions: [
|
||||
new ExpressionStub().withParameterNames(['parameter'], false),
|
||||
],
|
||||
args: new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('unnecessaryParameter', 'unnecessaryValue'),
|
||||
expectedError: 'parameter value(s) not provided for: "parameter" but used in code',
|
||||
},
|
||||
{
|
||||
name: 'multiple values are not provided',
|
||||
expressions: [
|
||||
new ExpressionStub().withParameterNames(['parameter1'], false),
|
||||
new ExpressionStub().withParameterNames(['parameter2', 'parameter3'], false),
|
||||
],
|
||||
args: new FunctionCallArgumentCollectionStub(),
|
||||
expectedError: 'parameter value(s) not provided for: "parameter1", "parameter2", "parameter3" but used in code',
|
||||
},
|
||||
{
|
||||
name: 'some values are provided',
|
||||
expressions: [
|
||||
new ExpressionStub().withParameterNames(['parameter1'], false),
|
||||
new ExpressionStub().withParameterNames(['parameter2', 'parameter3'], false),
|
||||
],
|
||||
args: new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('parameter2', 'value'),
|
||||
expectedError: 'parameter value(s) not provided for: "parameter1", "parameter3" but used in code',
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const code = 'non-important-code';
|
||||
const expressionParserMock = new ExpressionParserStub()
|
||||
.withResult(testCase.expressions);
|
||||
const sut = new SystemUnderTest(expressionParserMock);
|
||||
// act
|
||||
const act = () => sut.compileExpressions(code, testCase.args);
|
||||
// assert
|
||||
expect(act).to.throw(testCase.expectedError);
|
||||
});
|
||||
}
|
||||
});
|
||||
it('calls parser with expected code', () => {
|
||||
// arrange
|
||||
const expected = 'expected-code';
|
||||
const expressionParserMock = new ExpressionParserStub();
|
||||
const sut = new SystemUnderTest(expressionParserMock);
|
||||
const args = new FunctionCallArgumentCollectionStub();
|
||||
// act
|
||||
sut.compileExpressions(expected, args);
|
||||
// assert
|
||||
expect(expressionParserMock.callHistory).to.have.lengthOf(1);
|
||||
expect(expressionParserMock.callHistory[0]).to.equal(expected);
|
||||
describe('compileExpressions', () => {
|
||||
describe('returns code when it is empty or undefined', () => {
|
||||
// arrange
|
||||
const testCases = [{
|
||||
name: 'empty',
|
||||
value: '',
|
||||
}, {
|
||||
name: 'undefined',
|
||||
value: undefined,
|
||||
},
|
||||
];
|
||||
for (const test of testCases) {
|
||||
it(`given ${test.name}`, () => {
|
||||
const expected = test.value;
|
||||
const sut = new SystemUnderTest();
|
||||
const args = new FunctionCallArgumentCollectionStub();
|
||||
// act
|
||||
const value = sut.compileExpressions(test.value, args);
|
||||
// assert
|
||||
expect(value).to.equal(expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('combines expressions as expected', () => {
|
||||
// arrange
|
||||
const code = 'part1 {{ a }} part2 {{ b }} part3';
|
||||
const testCases = [
|
||||
{
|
||||
name: 'with ordered expressions',
|
||||
expressions: [
|
||||
new ExpressionStub().withPosition(6, 13).withEvaluatedResult('a'),
|
||||
new ExpressionStub().withPosition(20, 27).withEvaluatedResult('b'),
|
||||
],
|
||||
expected: 'part1 a part2 b part3',
|
||||
},
|
||||
{
|
||||
name: 'unordered expressions',
|
||||
expressions: [
|
||||
new ExpressionStub().withPosition(20, 27).withEvaluatedResult('b'),
|
||||
new ExpressionStub().withPosition(6, 13).withEvaluatedResult('a'),
|
||||
],
|
||||
expected: 'part1 a part2 b part3',
|
||||
},
|
||||
{
|
||||
name: 'with an optional expected argument that is not provided',
|
||||
expressions: [
|
||||
new ExpressionStub().withPosition(6, 13).withEvaluatedResult('a')
|
||||
.withParameterNames(['optionalParameter'], true),
|
||||
new ExpressionStub().withPosition(20, 27).withEvaluatedResult('b')
|
||||
.withParameterNames(['optionalParameterTwo'], true),
|
||||
],
|
||||
expected: 'part1 a part2 b part3',
|
||||
},
|
||||
{
|
||||
name: 'with no expressions',
|
||||
expressions: [],
|
||||
expected: code,
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const expressionParserMock = new ExpressionParserStub()
|
||||
.withResult(testCase.expressions);
|
||||
const args = new FunctionCallArgumentCollectionStub();
|
||||
const sut = new SystemUnderTest(expressionParserMock);
|
||||
// act
|
||||
const actual = sut.compileExpressions(code, args);
|
||||
// assert
|
||||
expect(actual).to.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('arguments', () => {
|
||||
it('passes arguments to expressions as expected', () => {
|
||||
// arrange
|
||||
const expected = new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('test-arg', 'test-value');
|
||||
const code = 'non-important';
|
||||
const expressions = [
|
||||
new ExpressionStub(),
|
||||
new ExpressionStub(),
|
||||
];
|
||||
const expressionParserMock = new ExpressionParserStub()
|
||||
.withResult(expressions);
|
||||
const sut = new SystemUnderTest(expressionParserMock);
|
||||
// act
|
||||
sut.compileExpressions(code, expected);
|
||||
// assert
|
||||
expect(expressions[0].callHistory).to.have.lengthOf(1);
|
||||
expect(expressions[0].callHistory[0].args).to.equal(expected);
|
||||
expect(expressions[1].callHistory).to.have.lengthOf(1);
|
||||
expect(expressions[1].callHistory[0].args).to.equal(expected);
|
||||
});
|
||||
it('throws if arguments is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined args, send empty collection instead';
|
||||
const args = undefined;
|
||||
const expressionParserMock = new ExpressionParserStub();
|
||||
const sut = new SystemUnderTest(expressionParserMock);
|
||||
// act
|
||||
const act = () => sut.compileExpressions('code', args);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('throws when expected argument is not provided but used in code', () => {
|
||||
// arrange
|
||||
const testCases = [
|
||||
{
|
||||
name: 'empty parameters',
|
||||
expressions: [
|
||||
new ExpressionStub().withParameterNames(['parameter'], false),
|
||||
],
|
||||
args: new FunctionCallArgumentCollectionStub(),
|
||||
expectedError: 'parameter value(s) not provided for: "parameter" but used in code',
|
||||
},
|
||||
{
|
||||
name: 'unnecessary parameter is provided',
|
||||
expressions: [
|
||||
new ExpressionStub().withParameterNames(['parameter'], false),
|
||||
],
|
||||
args: new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('unnecessaryParameter', 'unnecessaryValue'),
|
||||
expectedError: 'parameter value(s) not provided for: "parameter" but used in code',
|
||||
},
|
||||
{
|
||||
name: 'multiple values are not provided',
|
||||
expressions: [
|
||||
new ExpressionStub().withParameterNames(['parameter1'], false),
|
||||
new ExpressionStub().withParameterNames(['parameter2', 'parameter3'], false),
|
||||
],
|
||||
args: new FunctionCallArgumentCollectionStub(),
|
||||
expectedError: 'parameter value(s) not provided for: "parameter1", "parameter2", "parameter3" but used in code',
|
||||
},
|
||||
{
|
||||
name: 'some values are provided',
|
||||
expressions: [
|
||||
new ExpressionStub().withParameterNames(['parameter1'], false),
|
||||
new ExpressionStub().withParameterNames(['parameter2', 'parameter3'], false),
|
||||
],
|
||||
args: new FunctionCallArgumentCollectionStub()
|
||||
.withArgument('parameter2', 'value'),
|
||||
expectedError: 'parameter value(s) not provided for: "parameter1", "parameter3" but used in code',
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const code = 'non-important-code';
|
||||
const expressionParserMock = new ExpressionParserStub()
|
||||
.withResult(testCase.expressions);
|
||||
const sut = new SystemUnderTest(expressionParserMock);
|
||||
// act
|
||||
const act = () => sut.compileExpressions(code, testCase.args);
|
||||
// assert
|
||||
expect(act).to.throw(testCase.expectedError);
|
||||
});
|
||||
}
|
||||
});
|
||||
it('calls parser with expected code', () => {
|
||||
// arrange
|
||||
const expected = 'expected-code';
|
||||
const expressionParserMock = new ExpressionParserStub();
|
||||
const sut = new SystemUnderTest(expressionParserMock);
|
||||
const args = new FunctionCallArgumentCollectionStub();
|
||||
// act
|
||||
sut.compileExpressions(expected, args);
|
||||
// assert
|
||||
expect(expressionParserMock.callHistory).to.have.lengthOf(1);
|
||||
expect(expressionParserMock.callHistory[0]).to.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class SystemUnderTest extends ExpressionsCompiler {
|
||||
constructor(extractor: IExpressionParser = new ExpressionParserStub()) {
|
||||
super(extractor);
|
||||
}
|
||||
constructor(extractor: IExpressionParser = new ExpressionParserStub()) {
|
||||
super(extractor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,82 +6,82 @@ import { CompositeExpressionParser } from '@/application/Parser/Script/Compiler/
|
||||
import { ExpressionStub } from '@tests/unit/stubs/ExpressionStub';
|
||||
|
||||
describe('CompositeExpressionParser', () => {
|
||||
describe('ctor', () => {
|
||||
it('throws if one of the parsers is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined leaf';
|
||||
const parsers: readonly IExpressionParser[] = [ undefined, mockParser() ];
|
||||
// act
|
||||
const act = () => new CompositeExpressionParser(parsers);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('ctor', () => {
|
||||
it('throws if one of the parsers is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined leaf';
|
||||
const parsers: readonly IExpressionParser[] = [undefined, mockParser()];
|
||||
// act
|
||||
const act = () => new CompositeExpressionParser(parsers);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('findExpressions', () => {
|
||||
describe('returns result from parsers as expected', () => {
|
||||
// arrange
|
||||
const pool = [
|
||||
new ExpressionStub(), new ExpressionStub(), new ExpressionStub(),
|
||||
new ExpressionStub(), new ExpressionStub(),
|
||||
];
|
||||
const testCases = [
|
||||
{
|
||||
name: 'from single parsing none',
|
||||
parsers: [ mockParser() ],
|
||||
expected: [],
|
||||
},
|
||||
{
|
||||
name: 'from single parsing single',
|
||||
parsers: [ mockParser(pool[0]) ],
|
||||
expected: [ pool[0] ],
|
||||
},
|
||||
{
|
||||
name: 'from single parsing multiple',
|
||||
parsers: [ mockParser(pool[0], pool[1]) ],
|
||||
expected: [ pool[0], pool[1] ],
|
||||
},
|
||||
{
|
||||
name: 'from multiple parsers with each parsing single',
|
||||
parsers: [
|
||||
mockParser(pool[0]),
|
||||
mockParser(pool[1]),
|
||||
mockParser(pool[2]),
|
||||
],
|
||||
expected: [ pool[0], pool[1], pool[2] ],
|
||||
},
|
||||
{
|
||||
name: 'from multiple parsers with each parsing multiple',
|
||||
parsers: [
|
||||
mockParser(pool[0], pool[1]),
|
||||
mockParser(pool[2], pool[3], pool[4]) ],
|
||||
expected: [ pool[0], pool[1], pool[2], pool[3], pool[4] ],
|
||||
},
|
||||
{
|
||||
name: 'from multiple parsers with only some parsing',
|
||||
parsers: [
|
||||
mockParser(pool[0], pool[1]),
|
||||
mockParser(),
|
||||
mockParser(pool[2]),
|
||||
mockParser(),
|
||||
],
|
||||
expected: [ pool[0], pool[1], pool[2] ],
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const sut = new CompositeExpressionParser(testCase.parsers);
|
||||
// act
|
||||
const result = sut.findExpressions('non-important-code');
|
||||
// expect
|
||||
expect(result).to.deep.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('findExpressions', () => {
|
||||
describe('returns result from parsers as expected', () => {
|
||||
// arrange
|
||||
const pool = [
|
||||
new ExpressionStub(), new ExpressionStub(), new ExpressionStub(),
|
||||
new ExpressionStub(), new ExpressionStub(),
|
||||
];
|
||||
const testCases = [
|
||||
{
|
||||
name: 'from single parsing none',
|
||||
parsers: [mockParser()],
|
||||
expected: [],
|
||||
},
|
||||
{
|
||||
name: 'from single parsing single',
|
||||
parsers: [mockParser(pool[0])],
|
||||
expected: [pool[0]],
|
||||
},
|
||||
{
|
||||
name: 'from single parsing multiple',
|
||||
parsers: [mockParser(pool[0], pool[1])],
|
||||
expected: [pool[0], pool[1]],
|
||||
},
|
||||
{
|
||||
name: 'from multiple parsers with each parsing single',
|
||||
parsers: [
|
||||
mockParser(pool[0]),
|
||||
mockParser(pool[1]),
|
||||
mockParser(pool[2]),
|
||||
],
|
||||
expected: [pool[0], pool[1], pool[2]],
|
||||
},
|
||||
{
|
||||
name: 'from multiple parsers with each parsing multiple',
|
||||
parsers: [
|
||||
mockParser(pool[0], pool[1]),
|
||||
mockParser(pool[2], pool[3], pool[4])],
|
||||
expected: [pool[0], pool[1], pool[2], pool[3], pool[4]],
|
||||
},
|
||||
{
|
||||
name: 'from multiple parsers with only some parsing',
|
||||
parsers: [
|
||||
mockParser(pool[0], pool[1]),
|
||||
mockParser(),
|
||||
mockParser(pool[2]),
|
||||
mockParser(),
|
||||
],
|
||||
expected: [pool[0], pool[1], pool[2]],
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const sut = new CompositeExpressionParser(testCase.parsers);
|
||||
// act
|
||||
const result = sut.findExpressions('non-important-code');
|
||||
// expect
|
||||
expect(result).to.deep.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function mockParser(...result: IExpression[]): IExpressionParser {
|
||||
return {
|
||||
findExpressions: () => result,
|
||||
};
|
||||
return {
|
||||
findExpressions: () => result,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,133 +3,153 @@ import { expect } from 'chai';
|
||||
import { ExpressionRegexBuilder } from '@/application/Parser/Script/Compiler/Expressions/Parser/Regex/ExpressionRegexBuilder';
|
||||
|
||||
describe('ExpressionRegexBuilder', () => {
|
||||
describe('expectCharacters', () => {
|
||||
describe('escape single as expected', () => {
|
||||
const charactersToEscape = [ '.', '$' ];
|
||||
for (const character of charactersToEscape) {
|
||||
it(character, () => {
|
||||
runRegExTest(
|
||||
// act
|
||||
(act) => act.expectCharacters(character),
|
||||
// assert
|
||||
`\\${character}`);
|
||||
});
|
||||
}
|
||||
});
|
||||
it('escapes multiple as expected', () => {
|
||||
runRegExTest(
|
||||
// act
|
||||
(act) => act.expectCharacters('.I have no $$.'),
|
||||
// assert
|
||||
'\\.I have no \\$\\\$\\.');
|
||||
});
|
||||
it('adds as expected', () => {
|
||||
runRegExTest(
|
||||
// act
|
||||
(act) => act.expectCharacters('return as it is'),
|
||||
// assert
|
||||
'return as it is');
|
||||
describe('expectCharacters', () => {
|
||||
describe('escape single as expected', () => {
|
||||
const charactersToEscape = ['.', '$'];
|
||||
for (const character of charactersToEscape) {
|
||||
it(character, () => {
|
||||
runRegExTest(
|
||||
// act
|
||||
(act) => act.expectCharacters(character),
|
||||
// assert
|
||||
`\\${character}`,
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
it('expectOneOrMoreWhitespaces', () => {
|
||||
it('escapes multiple as expected', () => {
|
||||
runRegExTest(
|
||||
// act
|
||||
(act) => act.expectCharacters('.I have no $$.'),
|
||||
// assert
|
||||
'\\.I have no \\$\\$\\.',
|
||||
);
|
||||
});
|
||||
it('adds as expected', () => {
|
||||
runRegExTest(
|
||||
// act
|
||||
(act) => act.expectCharacters('return as it is'),
|
||||
// assert
|
||||
'return as it is',
|
||||
);
|
||||
});
|
||||
});
|
||||
it('expectOneOrMoreWhitespaces', () => {
|
||||
runRegExTest(
|
||||
// act
|
||||
(act) => act.expectOneOrMoreWhitespaces(),
|
||||
// assert
|
||||
'\\s+',
|
||||
);
|
||||
});
|
||||
it('matchPipeline', () => {
|
||||
runRegExTest(
|
||||
// act
|
||||
(act) => act.matchPipeline(),
|
||||
// assert
|
||||
'\\s*(\\|\\s*.+?)?',
|
||||
);
|
||||
});
|
||||
it('matchUntilFirstWhitespace', () => {
|
||||
runRegExTest(
|
||||
// act
|
||||
(act) => act.matchUntilFirstWhitespace(),
|
||||
// assert
|
||||
'([^|\\s]+)',
|
||||
);
|
||||
});
|
||||
it('matchAnythingExceptSurroundingWhitespaces', () => {
|
||||
runRegExTest(
|
||||
// act
|
||||
(act) => act.matchAnythingExceptSurroundingWhitespaces(),
|
||||
// assert
|
||||
'\\s*(.+?)\\s*',
|
||||
);
|
||||
});
|
||||
it('expectExpressionStart', () => {
|
||||
runRegExTest(
|
||||
// act
|
||||
(act) => act.expectExpressionStart(),
|
||||
// assert
|
||||
'{{\\s*',
|
||||
);
|
||||
});
|
||||
it('expectExpressionEnd', () => {
|
||||
runRegExTest(
|
||||
// act
|
||||
(act) => act.expectExpressionEnd(),
|
||||
// assert
|
||||
'\\s*}}',
|
||||
);
|
||||
});
|
||||
describe('buildRegExp', () => {
|
||||
it('sets global flag', () => {
|
||||
// arrange
|
||||
const expected = 'g';
|
||||
const sut = new ExpressionRegexBuilder()
|
||||
.expectOneOrMoreWhitespaces();
|
||||
// act
|
||||
const actual = sut.buildRegExp().flags;
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
describe('can combine multiple parts', () => {
|
||||
it('with', () => {
|
||||
runRegExTest(
|
||||
// act
|
||||
(act) => act.expectOneOrMoreWhitespaces(),
|
||||
// assert
|
||||
'\\s+');
|
||||
});
|
||||
it('matchPipeline', () => {
|
||||
(sut) => sut
|
||||
// act
|
||||
// {{ $with }}
|
||||
.expectExpressionStart()
|
||||
.expectCharacters('with')
|
||||
.expectOneOrMoreWhitespaces()
|
||||
.expectCharacters('$')
|
||||
.matchUntilFirstWhitespace()
|
||||
.expectExpressionEnd()
|
||||
// scope
|
||||
.matchAnythingExceptSurroundingWhitespaces()
|
||||
// {{ end }}
|
||||
.expectExpressionStart()
|
||||
.expectCharacters('end')
|
||||
.expectExpressionEnd(),
|
||||
// assert
|
||||
'{{\\s*with\\s+\\$([^|\\s]+)\\s*}}\\s*(.+?)\\s*{{\\s*end\\s*}}',
|
||||
);
|
||||
});
|
||||
it('scoped substitution', () => {
|
||||
runRegExTest(
|
||||
// act
|
||||
(act) => act.matchPipeline(),
|
||||
// assert
|
||||
'\\s*(\\|\\s*.+?)?');
|
||||
});
|
||||
it('matchUntilFirstWhitespace', () => {
|
||||
(sut) => sut
|
||||
// act
|
||||
.expectExpressionStart().expectCharacters('.')
|
||||
.matchPipeline()
|
||||
.expectExpressionEnd(),
|
||||
// assert
|
||||
'{{\\s*\\.\\s*(\\|\\s*.+?)?\\s*}}',
|
||||
);
|
||||
});
|
||||
it('parameter substitution', () => {
|
||||
runRegExTest(
|
||||
// act
|
||||
(act) => act.matchUntilFirstWhitespace(),
|
||||
// assert
|
||||
'([^|\\s]+)');
|
||||
});
|
||||
it('matchAnythingExceptSurroundingWhitespaces', () => {
|
||||
runRegExTest(
|
||||
// act
|
||||
(act) => act.matchAnythingExceptSurroundingWhitespaces(),
|
||||
// assert
|
||||
'\\s*(.+?)\\s*');
|
||||
});
|
||||
it('expectExpressionStart', () => {
|
||||
runRegExTest(
|
||||
// act
|
||||
(act) => act.expectExpressionStart(),
|
||||
// assert
|
||||
'{{\\s*');
|
||||
});
|
||||
it('expectExpressionEnd', () => {
|
||||
runRegExTest(
|
||||
// act
|
||||
(act) => act.expectExpressionEnd(),
|
||||
// assert
|
||||
'\\s*}}');
|
||||
});
|
||||
describe('buildRegExp', () => {
|
||||
it('sets global flag', () => {
|
||||
// arrange
|
||||
const expected = 'g';
|
||||
const sut = new ExpressionRegexBuilder()
|
||||
.expectOneOrMoreWhitespaces();
|
||||
// act
|
||||
const actual = sut.buildRegExp().flags;
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
describe('can combine multiple parts', () => {
|
||||
it('with', () => {
|
||||
runRegExTest((sut) => sut
|
||||
// act
|
||||
.expectExpressionStart().expectCharacters('with').expectOneOrMoreWhitespaces().expectCharacters('$')
|
||||
.matchUntilFirstWhitespace()
|
||||
.expectExpressionEnd()
|
||||
.matchAnythingExceptSurroundingWhitespaces()
|
||||
.expectExpressionStart().expectCharacters('end').expectExpressionEnd(),
|
||||
// assert
|
||||
'{{\\s*with\\s+\\$([^|\\s]+)\\s*}}\\s*(.+?)\\s*{{\\s*end\\s*}}',
|
||||
);
|
||||
});
|
||||
it('scoped substitution', () => {
|
||||
runRegExTest((sut) => sut
|
||||
// act
|
||||
.expectExpressionStart().expectCharacters('.')
|
||||
.matchPipeline()
|
||||
.expectExpressionEnd(),
|
||||
// assert
|
||||
'{{\\s*\\.\\s*(\\|\\s*.+?)?\\s*}}',
|
||||
);
|
||||
});
|
||||
it('parameter substitution', () => {
|
||||
runRegExTest((sut) => sut
|
||||
// act
|
||||
.expectExpressionStart().expectCharacters('$')
|
||||
.matchUntilFirstWhitespace()
|
||||
.matchPipeline()
|
||||
.expectExpressionEnd(),
|
||||
// assert
|
||||
'{{\\s*\\$([^|\\s]+)\\s*(\\|\\s*.+?)?\\s*}}',
|
||||
);
|
||||
});
|
||||
});
|
||||
(sut) => sut
|
||||
// act
|
||||
.expectExpressionStart().expectCharacters('$')
|
||||
.matchUntilFirstWhitespace()
|
||||
.matchPipeline()
|
||||
.expectExpressionEnd(),
|
||||
// assert
|
||||
'{{\\s*\\$([^|\\s]+)\\s*(\\|\\s*.+?)?\\s*}}',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function runRegExTest(
|
||||
act: (sut: ExpressionRegexBuilder) => ExpressionRegexBuilder,
|
||||
expected: string,
|
||||
) {
|
||||
// arrange
|
||||
const sut = new ExpressionRegexBuilder();
|
||||
// act
|
||||
const actual = act(sut).buildRegExp().source;
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
act: (sut: ExpressionRegexBuilder) => ExpressionRegexBuilder,
|
||||
expected: string,
|
||||
) {
|
||||
// arrange
|
||||
const sut = new ExpressionRegexBuilder();
|
||||
// act
|
||||
const actual = act(sut).buildRegExp().source;
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
}
|
||||
|
||||
@@ -6,145 +6,148 @@ import { ExpressionPosition } from '@/application/Parser/Script/Compiler/Express
|
||||
import { FunctionParameterStub } from '@tests/unit/stubs/FunctionParameterStub';
|
||||
|
||||
describe('RegexParser', () => {
|
||||
describe('findExpressions', () => {
|
||||
describe('throws when code is unexpected', () => {
|
||||
// arrange
|
||||
const testCases = [
|
||||
{
|
||||
name: 'undefined',
|
||||
value: undefined,
|
||||
expectedError: 'undefined code',
|
||||
},
|
||||
{
|
||||
name: 'empty',
|
||||
value: '',
|
||||
expectedError: 'undefined code',
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(`given ${testCase.name}`, () => {
|
||||
const sut = new RegexParserConcrete(/unimportant/);
|
||||
// act
|
||||
const act = () => sut.findExpressions(testCase.value);
|
||||
// assert
|
||||
expect(act).to.throw(testCase.expectedError);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('matches regex as expected', () => {
|
||||
// arrange
|
||||
const testCases = [
|
||||
{
|
||||
name: 'returns no result when regex does not match',
|
||||
regex: /hello/g,
|
||||
code: 'world',
|
||||
},
|
||||
{
|
||||
name: 'returns expected when regex matches single',
|
||||
regex: /hello/g,
|
||||
code: 'hello world',
|
||||
},
|
||||
{
|
||||
name: 'returns expected when regex matches multiple',
|
||||
regex: /l/g,
|
||||
code: 'hello world',
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const expected = Array.from(testCase.code.matchAll(testCase.regex));
|
||||
const matches = new Array<RegExpMatchArray>();
|
||||
const builder = (m: RegExpMatchArray): IPrimitiveExpression => {
|
||||
matches.push(m);
|
||||
return mockPrimitiveExpression();
|
||||
};
|
||||
const sut = new RegexParserConcrete(testCase.regex, builder);
|
||||
// act
|
||||
const expressions = sut.findExpressions(testCase.code);
|
||||
// assert
|
||||
expect(expressions).to.have.lengthOf(matches.length);
|
||||
expect(matches).to.deep.equal(expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
it('sets evaluator as expected', () => {
|
||||
// arrange
|
||||
const expected = getEvaluatorStub();
|
||||
const regex = /hello/g;
|
||||
const code = 'hello';
|
||||
const builder = (): IPrimitiveExpression => ({
|
||||
evaluator: expected,
|
||||
});
|
||||
const sut = new RegexParserConcrete(regex, builder);
|
||||
// act
|
||||
const expressions = sut.findExpressions(code);
|
||||
// assert
|
||||
expect(expressions).to.have.lengthOf(1);
|
||||
expect(expressions[0].evaluate === expected);
|
||||
});
|
||||
it('sets parameters as expected', () => {
|
||||
// arrange
|
||||
const expected = [
|
||||
new FunctionParameterStub().withName('parameter1').withOptionality(true),
|
||||
new FunctionParameterStub().withName('parameter2').withOptionality(false),
|
||||
];
|
||||
const regex = /hello/g;
|
||||
const code = 'hello';
|
||||
const builder = (): IPrimitiveExpression => ({
|
||||
evaluator: getEvaluatorStub(),
|
||||
parameters: expected,
|
||||
});
|
||||
const sut = new RegexParserConcrete(regex, builder);
|
||||
// act
|
||||
const expressions = sut.findExpressions(code);
|
||||
// assert
|
||||
expect(expressions).to.have.lengthOf(1);
|
||||
expect(expressions[0].parameters.all).to.deep.equal(expected);
|
||||
});
|
||||
it('sets expected position', () => {
|
||||
// arrange
|
||||
const code = 'mate date in state is fate';
|
||||
const regex = /ate/g;
|
||||
const expected = [
|
||||
new ExpressionPosition(1, 4),
|
||||
new ExpressionPosition(6, 9),
|
||||
new ExpressionPosition(15, 18),
|
||||
new ExpressionPosition(23, 26),
|
||||
];
|
||||
const sut = new RegexParserConcrete(regex);
|
||||
// act
|
||||
const expressions = sut.findExpressions(code);
|
||||
// assert
|
||||
const actual = expressions.map((e) => e.position);
|
||||
expect(actual).to.deep.equal(expected);
|
||||
describe('findExpressions', () => {
|
||||
describe('throws when code is unexpected', () => {
|
||||
// arrange
|
||||
const testCases = [
|
||||
{
|
||||
name: 'undefined',
|
||||
value: undefined,
|
||||
expectedError: 'undefined code',
|
||||
},
|
||||
{
|
||||
name: 'empty',
|
||||
value: '',
|
||||
expectedError: 'undefined code',
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(`given ${testCase.name}`, () => {
|
||||
const sut = new RegexParserConcrete(/unimportant/);
|
||||
// act
|
||||
const act = () => sut.findExpressions(testCase.value);
|
||||
// assert
|
||||
expect(act).to.throw(testCase.expectedError);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('matches regex as expected', () => {
|
||||
// arrange
|
||||
const testCases = [
|
||||
{
|
||||
name: 'returns no result when regex does not match',
|
||||
regex: /hello/g,
|
||||
code: 'world',
|
||||
},
|
||||
{
|
||||
name: 'returns expected when regex matches single',
|
||||
regex: /hello/g,
|
||||
code: 'hello world',
|
||||
},
|
||||
{
|
||||
name: 'returns expected when regex matches multiple',
|
||||
regex: /l/g,
|
||||
code: 'hello world',
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const expected = Array.from(testCase.code.matchAll(testCase.regex));
|
||||
const matches = new Array<RegExpMatchArray>();
|
||||
const builder = (m: RegExpMatchArray): IPrimitiveExpression => {
|
||||
matches.push(m);
|
||||
return mockPrimitiveExpression();
|
||||
};
|
||||
const sut = new RegexParserConcrete(testCase.regex, builder);
|
||||
// act
|
||||
const expressions = sut.findExpressions(testCase.code);
|
||||
// assert
|
||||
expect(expressions).to.have.lengthOf(matches.length);
|
||||
expect(matches).to.deep.equal(expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
it('sets evaluator as expected', () => {
|
||||
// arrange
|
||||
const expected = getEvaluatorStub();
|
||||
const regex = /hello/g;
|
||||
const code = 'hello';
|
||||
const builder = (): IPrimitiveExpression => ({
|
||||
evaluator: expected,
|
||||
});
|
||||
const sut = new RegexParserConcrete(regex, builder);
|
||||
// act
|
||||
const expressions = sut.findExpressions(code);
|
||||
// assert
|
||||
expect(expressions).to.have.lengthOf(1);
|
||||
expect(expressions[0].evaluate === expected);
|
||||
});
|
||||
it('sets parameters as expected', () => {
|
||||
// arrange
|
||||
const expected = [
|
||||
new FunctionParameterStub().withName('parameter1').withOptionality(true),
|
||||
new FunctionParameterStub().withName('parameter2').withOptionality(false),
|
||||
];
|
||||
const regex = /hello/g;
|
||||
const code = 'hello';
|
||||
const builder = (): IPrimitiveExpression => ({
|
||||
evaluator: getEvaluatorStub(),
|
||||
parameters: expected,
|
||||
});
|
||||
const sut = new RegexParserConcrete(regex, builder);
|
||||
// act
|
||||
const expressions = sut.findExpressions(code);
|
||||
// assert
|
||||
expect(expressions).to.have.lengthOf(1);
|
||||
expect(expressions[0].parameters.all).to.deep.equal(expected);
|
||||
});
|
||||
it('sets expected position', () => {
|
||||
// arrange
|
||||
const code = 'mate date in state is fate';
|
||||
const regex = /ate/g;
|
||||
const expected = [
|
||||
new ExpressionPosition(1, 4),
|
||||
new ExpressionPosition(6, 9),
|
||||
new ExpressionPosition(15, 18),
|
||||
new ExpressionPosition(23, 26),
|
||||
];
|
||||
const sut = new RegexParserConcrete(regex);
|
||||
// act
|
||||
const expressions = sut.findExpressions(code);
|
||||
// assert
|
||||
const actual = expressions.map((e) => e.position);
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function mockBuilder(): (match: RegExpMatchArray) => IPrimitiveExpression {
|
||||
return () => ({
|
||||
evaluator: getEvaluatorStub(),
|
||||
});
|
||||
return () => ({
|
||||
evaluator: getEvaluatorStub(),
|
||||
});
|
||||
}
|
||||
function getEvaluatorStub(): ExpressionEvaluator {
|
||||
return () => undefined;
|
||||
return () => undefined;
|
||||
}
|
||||
|
||||
function mockPrimitiveExpression(): IPrimitiveExpression {
|
||||
return {
|
||||
evaluator: getEvaluatorStub(),
|
||||
};
|
||||
return {
|
||||
evaluator: getEvaluatorStub(),
|
||||
};
|
||||
}
|
||||
|
||||
class RegexParserConcrete extends RegexParser {
|
||||
protected regex: RegExp;
|
||||
public constructor(
|
||||
regex: RegExp,
|
||||
private readonly builder = mockBuilder()) {
|
||||
super();
|
||||
this.regex = regex;
|
||||
}
|
||||
protected buildExpression(match: RegExpMatchArray): IPrimitiveExpression {
|
||||
return this.builder(match);
|
||||
}
|
||||
protected regex: RegExp;
|
||||
|
||||
public constructor(
|
||||
regex: RegExp,
|
||||
private readonly builder = mockBuilder(),
|
||||
) {
|
||||
super();
|
||||
this.regex = regex;
|
||||
}
|
||||
|
||||
protected buildExpression(match: RegExpMatchArray): IPrimitiveExpression {
|
||||
return this.builder(match);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
import 'mocha';
|
||||
import { runPipeTests } from './PipeTestRunner';
|
||||
import { EscapeDoubleQuotes } from '@/application/Parser/Script/Compiler/Expressions/Pipes/PipeDefinitions/EscapeDoubleQuotes';
|
||||
import { runPipeTests } from './PipeTestRunner';
|
||||
|
||||
describe('EscapeDoubleQuotes', () => {
|
||||
// arrange
|
||||
const sut = new EscapeDoubleQuotes();
|
||||
// act
|
||||
runPipeTests(sut, [
|
||||
{
|
||||
name: 'using "',
|
||||
input: 'hello "world"',
|
||||
expectedOutput: 'hello "^""world"^""',
|
||||
},
|
||||
{
|
||||
name: 'not using any double quotes',
|
||||
input: 'hello world',
|
||||
expectedOutput: 'hello world',
|
||||
},
|
||||
{
|
||||
name: 'consecutive double quotes',
|
||||
input: '""hello world""',
|
||||
expectedOutput: '"^"""^""hello world"^"""^""',
|
||||
},
|
||||
{
|
||||
name: 'returns undefined when if input is undefined',
|
||||
input: undefined,
|
||||
expectedOutput: undefined,
|
||||
},
|
||||
]);
|
||||
// arrange
|
||||
const sut = new EscapeDoubleQuotes();
|
||||
// act
|
||||
runPipeTests(sut, [
|
||||
{
|
||||
name: 'using "',
|
||||
input: 'hello "world"',
|
||||
expectedOutput: 'hello "^""world"^""',
|
||||
},
|
||||
{
|
||||
name: 'not using any double quotes',
|
||||
input: 'hello world',
|
||||
expectedOutput: 'hello world',
|
||||
},
|
||||
{
|
||||
name: 'consecutive double quotes',
|
||||
input: '""hello world""',
|
||||
expectedOutput: '"^"""^""hello world"^"""^""',
|
||||
},
|
||||
{
|
||||
name: 'returns undefined when if input is undefined',
|
||||
input: undefined,
|
||||
expectedOutput: undefined,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -3,462 +3,461 @@ import { InlinePowerShell } from '@/application/Parser/Script/Compiler/Expressio
|
||||
import { IPipeTestCase, runPipeTests } from './PipeTestRunner';
|
||||
|
||||
describe('InlinePowerShell', () => {
|
||||
// arrange
|
||||
const sut = new InlinePowerShell();
|
||||
// act
|
||||
runPipeTests(sut, [
|
||||
{
|
||||
name: 'returns undefined when if input is undefined',
|
||||
input: undefined,
|
||||
expectedOutput: undefined,
|
||||
},
|
||||
...prefixTests('newline', getNewLineCases()),
|
||||
...prefixTests('comment', getCommentCases()),
|
||||
...prefixTests('here-string', hereStringCases()),
|
||||
...prefixTests('backtick', backTickCases()),
|
||||
]);
|
||||
// arrange
|
||||
const sut = new InlinePowerShell();
|
||||
// act
|
||||
runPipeTests(sut, [
|
||||
{
|
||||
name: 'returns undefined when if input is undefined',
|
||||
input: undefined,
|
||||
expectedOutput: undefined,
|
||||
},
|
||||
...prefixTests('newline', getNewLineCases()),
|
||||
...prefixTests('comment', getCommentCases()),
|
||||
...prefixTests('here-string', hereStringCases()),
|
||||
...prefixTests('backtick', backTickCases()),
|
||||
]);
|
||||
});
|
||||
|
||||
function hereStringCases(): IPipeTestCase[] {
|
||||
const expectLinesInDoubleQuotes = (...lines: string[]) => lines.join('`r`n');
|
||||
const expectLinesInSingleQuotes = (...lines: string[]) => lines.join('\'+"`r`n"+\'');
|
||||
return [
|
||||
{
|
||||
name: 'adds newlines for double quotes',
|
||||
input: getWindowsLines(
|
||||
'@"',
|
||||
'Lorem',
|
||||
'ipsum',
|
||||
'dolor sit amet',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: expectLinesInDoubleQuotes(
|
||||
'"Lorem',
|
||||
'ipsum',
|
||||
'dolor sit amet"',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'adds newlines for single quotes',
|
||||
input: getWindowsLines(
|
||||
'@\'',
|
||||
'Lorem',
|
||||
'ipsum',
|
||||
'dolor sit amet',
|
||||
'\'@',
|
||||
),
|
||||
expectedOutput: expectLinesInSingleQuotes(
|
||||
'\'Lorem',
|
||||
'ipsum',
|
||||
'dolor sit amet\'',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'does not match with character after here string header',
|
||||
input: getWindowsLines(
|
||||
'@" invalid syntax',
|
||||
'I will not be processed as here-string',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'@" invalid syntax',
|
||||
'I will not be processed as here-string',
|
||||
'"@',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'does not match if there\'s character before here-string terminator',
|
||||
input: getWindowsLines(
|
||||
'@\'',
|
||||
'do not match here',
|
||||
' \'@',
|
||||
'character \'@',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'@\'',
|
||||
'do not match here',
|
||||
' \'@',
|
||||
'character \'@',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'does not match with different here-string header/terminator',
|
||||
input: getWindowsLines(
|
||||
'@\'',
|
||||
'lorem',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'@\'',
|
||||
'lorem',
|
||||
'"@',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'matches with inner single quoted here-string',
|
||||
input: getWindowsLines(
|
||||
'$hasInnerDoubleQuotedTerminator = @"',
|
||||
'inner text',
|
||||
'@\'',
|
||||
'inner terminator text',
|
||||
'\'@',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: expectLinesInDoubleQuotes(
|
||||
'$hasInnerDoubleQuotedTerminator = "inner text',
|
||||
'@\'',
|
||||
'inner terminator text',
|
||||
'\'@"',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'matches with inner double quoted string',
|
||||
input: getWindowsLines(
|
||||
'$hasInnerSingleQuotedTerminator = @\'',
|
||||
'inner text',
|
||||
'@"',
|
||||
'inner terminator text',
|
||||
'"@',
|
||||
'\'@',
|
||||
),
|
||||
expectedOutput: expectLinesInSingleQuotes(
|
||||
'$hasInnerSingleQuotedTerminator = \'inner text',
|
||||
'@"',
|
||||
'inner terminator text',
|
||||
'"@\'',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'matches if there\'s character after here-string terminator',
|
||||
input: getWindowsLines(
|
||||
'@\'',
|
||||
'lorem',
|
||||
'\'@ after',
|
||||
),
|
||||
expectedOutput: expectLinesInSingleQuotes(
|
||||
'\'lorem\' after',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'escapes double quotes inside double quotes',
|
||||
input: getWindowsLines(
|
||||
'@"',
|
||||
'For help, type "get-help"',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: '"For help, type `"get-help`""',
|
||||
},
|
||||
{
|
||||
name: 'escapes single quotes inside single quotes',
|
||||
input: getWindowsLines(
|
||||
'@\'',
|
||||
'For help, type \'get-help\'',
|
||||
'\'@',
|
||||
),
|
||||
expectedOutput: '\'For help, type \'\'get-help\'\'\'',
|
||||
},
|
||||
{
|
||||
name: 'converts when here-string header is not at line start',
|
||||
input: getWindowsLines(
|
||||
'$page = [XML] @"',
|
||||
'multi-lined',
|
||||
'and "quoted"',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: expectLinesInDoubleQuotes(
|
||||
'$page = [XML] "multi-lined',
|
||||
'and `"quoted`""',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'trims after here-string header',
|
||||
input: getWindowsLines(
|
||||
'@" \t',
|
||||
'text with whitespaces at here-string start',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: '"text with whitespaces at here-string start"',
|
||||
},
|
||||
{
|
||||
name: 'preserves whitespaces in lines',
|
||||
input: getWindowsLines(
|
||||
'@\'',
|
||||
'\ttext with tabs around\t\t',
|
||||
' text with whitespaces around ',
|
||||
'\'@',
|
||||
),
|
||||
expectedOutput: expectLinesInSingleQuotes(
|
||||
'\'\ttext with tabs around\t\t',
|
||||
' text with whitespaces around \'',
|
||||
),
|
||||
},
|
||||
];
|
||||
const expectLinesInDoubleQuotes = (...lines: string[]) => lines.join('`r`n');
|
||||
const expectLinesInSingleQuotes = (...lines: string[]) => lines.join('\'+"`r`n"+\'');
|
||||
return [
|
||||
{
|
||||
name: 'adds newlines for double quotes',
|
||||
input: getWindowsLines(
|
||||
'@"',
|
||||
'Lorem',
|
||||
'ipsum',
|
||||
'dolor sit amet',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: expectLinesInDoubleQuotes(
|
||||
'"Lorem',
|
||||
'ipsum',
|
||||
'dolor sit amet"',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'adds newlines for single quotes',
|
||||
input: getWindowsLines(
|
||||
'@\'',
|
||||
'Lorem',
|
||||
'ipsum',
|
||||
'dolor sit amet',
|
||||
'\'@',
|
||||
),
|
||||
expectedOutput: expectLinesInSingleQuotes(
|
||||
'\'Lorem',
|
||||
'ipsum',
|
||||
'dolor sit amet\'',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'does not match with character after here string header',
|
||||
input: getWindowsLines(
|
||||
'@" invalid syntax',
|
||||
'I will not be processed as here-string',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'@" invalid syntax',
|
||||
'I will not be processed as here-string',
|
||||
'"@',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'does not match if there\'s character before here-string terminator',
|
||||
input: getWindowsLines(
|
||||
'@\'',
|
||||
'do not match here',
|
||||
' \'@',
|
||||
'character \'@',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'@\'',
|
||||
'do not match here',
|
||||
' \'@',
|
||||
'character \'@',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'does not match with different here-string header/terminator',
|
||||
input: getWindowsLines(
|
||||
'@\'',
|
||||
'lorem',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'@\'',
|
||||
'lorem',
|
||||
'"@',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'matches with inner single quoted here-string',
|
||||
input: getWindowsLines(
|
||||
'$hasInnerDoubleQuotedTerminator = @"',
|
||||
'inner text',
|
||||
'@\'',
|
||||
'inner terminator text',
|
||||
'\'@',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: expectLinesInDoubleQuotes(
|
||||
'$hasInnerDoubleQuotedTerminator = "inner text',
|
||||
'@\'',
|
||||
'inner terminator text',
|
||||
'\'@"',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'matches with inner double quoted string',
|
||||
input: getWindowsLines(
|
||||
'$hasInnerSingleQuotedTerminator = @\'',
|
||||
'inner text',
|
||||
'@"',
|
||||
'inner terminator text',
|
||||
'"@',
|
||||
'\'@',
|
||||
),
|
||||
expectedOutput: expectLinesInSingleQuotes(
|
||||
'$hasInnerSingleQuotedTerminator = \'inner text',
|
||||
'@"',
|
||||
'inner terminator text',
|
||||
'"@\'',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'matches if there\'s character after here-string terminator',
|
||||
input: getWindowsLines(
|
||||
'@\'',
|
||||
'lorem',
|
||||
'\'@ after',
|
||||
),
|
||||
expectedOutput: expectLinesInSingleQuotes(
|
||||
'\'lorem\' after',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'escapes double quotes inside double quotes',
|
||||
input: getWindowsLines(
|
||||
'@"',
|
||||
'For help, type "get-help"',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: '"For help, type `"get-help`""',
|
||||
},
|
||||
{
|
||||
name: 'escapes single quotes inside single quotes',
|
||||
input: getWindowsLines(
|
||||
'@\'',
|
||||
'For help, type \'get-help\'',
|
||||
'\'@',
|
||||
),
|
||||
expectedOutput: '\'For help, type \'\'get-help\'\'\'',
|
||||
},
|
||||
{
|
||||
name: 'converts when here-string header is not at line start',
|
||||
input: getWindowsLines(
|
||||
'$page = [XML] @"',
|
||||
'multi-lined',
|
||||
'and "quoted"',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: expectLinesInDoubleQuotes(
|
||||
'$page = [XML] "multi-lined',
|
||||
'and `"quoted`""',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'trims after here-string header',
|
||||
input: getWindowsLines(
|
||||
'@" \t',
|
||||
'text with whitespaces at here-string start',
|
||||
'"@',
|
||||
),
|
||||
expectedOutput: '"text with whitespaces at here-string start"',
|
||||
},
|
||||
{
|
||||
name: 'preserves whitespaces in lines',
|
||||
input: getWindowsLines(
|
||||
'@\'',
|
||||
'\ttext with tabs around\t\t',
|
||||
' text with whitespaces around ',
|
||||
'\'@',
|
||||
),
|
||||
expectedOutput: expectLinesInSingleQuotes(
|
||||
'\'\ttext with tabs around\t\t',
|
||||
' text with whitespaces around \'',
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function backTickCases(): IPipeTestCase[] {
|
||||
return [
|
||||
{
|
||||
name: 'wraps newlines with trailing backtick',
|
||||
input: getWindowsLines(
|
||||
'Get-Service * `',
|
||||
'| Format-Table -AutoSize',
|
||||
),
|
||||
expectedOutput: 'Get-Service * | Format-Table -AutoSize',
|
||||
},
|
||||
{
|
||||
name: 'wraps newlines with trailing backtick and different line endings',
|
||||
input: 'Get-Service `\n'
|
||||
+ '* `\r'
|
||||
+ '| Sort-Object StartType `\r\n'
|
||||
+ '| Format-Table -AutoSize'
|
||||
,
|
||||
expectedOutput: 'Get-Service * | Sort-Object StartType | Format-Table -AutoSize',
|
||||
},
|
||||
{
|
||||
name: 'trims tabs and whitespaces on next lines when wrapping with trailing backtick',
|
||||
input: getWindowsLines(
|
||||
'Get-Service * `',
|
||||
'\t| Sort-Object StartType `',
|
||||
' | Format-Table -AutoSize',
|
||||
),
|
||||
expectedOutput: 'Get-Service * | Sort-Object StartType | Format-Table -AutoSize',
|
||||
},
|
||||
{
|
||||
name: 'does not wrap without whitespace before backtick',
|
||||
input: getWindowsLines(
|
||||
'Get-Service *`',
|
||||
'| Format-Table -AutoSize',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'Get-Service *`',
|
||||
'| Format-Table -AutoSize',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'does not wrap with characters after',
|
||||
input: getWindowsLines(
|
||||
'line start ` after',
|
||||
'should not be wrapped',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'line start ` after',
|
||||
'should not be wrapped',
|
||||
),
|
||||
},
|
||||
];
|
||||
return [
|
||||
{
|
||||
name: 'wraps newlines with trailing backtick',
|
||||
input: getWindowsLines(
|
||||
'Get-Service * `',
|
||||
'| Format-Table -AutoSize',
|
||||
),
|
||||
expectedOutput: 'Get-Service * | Format-Table -AutoSize',
|
||||
},
|
||||
{
|
||||
name: 'wraps newlines with trailing backtick and different line endings',
|
||||
input: 'Get-Service `\n'
|
||||
+ '* `\r'
|
||||
+ '| Sort-Object StartType `\r\n'
|
||||
+ '| Format-Table -AutoSize',
|
||||
expectedOutput: 'Get-Service * | Sort-Object StartType | Format-Table -AutoSize',
|
||||
},
|
||||
{
|
||||
name: 'trims tabs and whitespaces on next lines when wrapping with trailing backtick',
|
||||
input: getWindowsLines(
|
||||
'Get-Service * `',
|
||||
'\t| Sort-Object StartType `',
|
||||
' | Format-Table -AutoSize',
|
||||
),
|
||||
expectedOutput: 'Get-Service * | Sort-Object StartType | Format-Table -AutoSize',
|
||||
},
|
||||
{
|
||||
name: 'does not wrap without whitespace before backtick',
|
||||
input: getWindowsLines(
|
||||
'Get-Service *`',
|
||||
'| Format-Table -AutoSize',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'Get-Service *`',
|
||||
'| Format-Table -AutoSize',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'does not wrap with characters after',
|
||||
input: getWindowsLines(
|
||||
'line start ` after',
|
||||
'should not be wrapped',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'line start ` after',
|
||||
'should not be wrapped',
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function getCommentCases(): IPipeTestCase[] {
|
||||
return [
|
||||
{
|
||||
name: 'converts hash comments in the line end',
|
||||
input: getWindowsLines(
|
||||
'$text = "Hello"\t# Comment after tab',
|
||||
'$text+= #Comment without space after hash',
|
||||
'Write-Host $text# Comment without space before hash',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$text = "Hello"\t<# Comment after tab #>',
|
||||
'$text+= <# Comment without space after hash #>',
|
||||
'Write-Host $text<# Comment without space before hash #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'converts hash comment line',
|
||||
input: getWindowsLines(
|
||||
'# Comment in first line',
|
||||
'Write-Host "Hello"',
|
||||
'# Comment in the middle',
|
||||
'Write-Host "World"',
|
||||
'# Consecutive comments',
|
||||
'# Last line comment without line ending in the end',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'<# Comment in first line #>',
|
||||
'Write-Host "Hello"',
|
||||
'<# Comment in the middle #>',
|
||||
'Write-Host "World"',
|
||||
'<# Consecutive comments #>',
|
||||
'<# Last line comment without line ending in the end #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'can convert comment with inline comment parts inside',
|
||||
input: getWindowsLines(
|
||||
'$text+= #Comment with < inside',
|
||||
'$text+= #Comment ending with >',
|
||||
'$text+= #Comment with <# inline comment #>',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$text+= <# Comment with < inside #>',
|
||||
'$text+= <# Comment ending with > #>',
|
||||
'$text+= <# Comment with <# inline comment #> #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'can convert comment with inline comment parts around', // Pretty uncommon
|
||||
input: getWindowsLines(
|
||||
'Write-Host "hi" # Comment ending line inline comment but not one #>',
|
||||
'Write-Host "hi" #>Comment starting like inline comment end but not one',
|
||||
// Following line does not compile as valid PowerShell referring to missing #> for inline comment
|
||||
'Write-Host "hi" <#Comment starting like inline comment start but not one',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'Write-Host "hi" <# Comment ending line inline comment but not one #> #>',
|
||||
'Write-Host "hi" <# >Comment starting like inline comment end but not one #>',
|
||||
'Write-Host "hi" <<# Comment starting like inline comment start but not one #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'converts empty hash comment',
|
||||
input: getWindowsLines(
|
||||
'Write-Host "Comment without text" #',
|
||||
'Write-Host "Non-empty line"',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'Write-Host "Comment without text" <##>',
|
||||
'Write-Host "Non-empty line"',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'adds whitespaces around to match',
|
||||
input: getWindowsLines(
|
||||
'#Comment line with no whitespaces around',
|
||||
'Write-Host "Hello"#Comment in the end with no whitespaces around',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'<# Comment line with no whitespaces around #>',
|
||||
'Write-Host "Hello"<# Comment in the end with no whitespaces around #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'trims whitespaces around comment',
|
||||
input: getWindowsLines(
|
||||
'# Comment with whitespaces around ',
|
||||
'#\tComment with tabs around\t\t',
|
||||
'#\t Comment with tabs and whitespaces around \t \t',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'<# Comment with whitespaces around #>',
|
||||
'<# Comment with tabs around #>',
|
||||
'<# Comment with tabs and whitespaces around #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'does not convert block comments',
|
||||
input: getWindowsLines(
|
||||
'$text = "Hello"\t<# block comment #> + "World"',
|
||||
'$text = "Hello"\t+<#comment#>"World"',
|
||||
'<# Block comment in a line #>',
|
||||
'Write-Host "Hello world <# Block comment in the end of line #>',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$text = "Hello"\t<# block comment #> + "World"',
|
||||
'$text = "Hello"\t+<#comment#>"World"',
|
||||
'<# Block comment in a line #>',
|
||||
'Write-Host "Hello world <# Block comment in the end of line #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'does not process if there are no multi lines',
|
||||
input: 'Write-Host \"expected\" # as it is!',
|
||||
expectedOutput: 'Write-Host \"expected\" # as it is!',
|
||||
},
|
||||
];
|
||||
return [
|
||||
{
|
||||
name: 'converts hash comments in the line end',
|
||||
input: getWindowsLines(
|
||||
'$text = "Hello"\t# Comment after tab',
|
||||
'$text+= #Comment without space after hash',
|
||||
'Write-Host $text# Comment without space before hash',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$text = "Hello"\t<# Comment after tab #>',
|
||||
'$text+= <# Comment without space after hash #>',
|
||||
'Write-Host $text<# Comment without space before hash #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'converts hash comment line',
|
||||
input: getWindowsLines(
|
||||
'# Comment in first line',
|
||||
'Write-Host "Hello"',
|
||||
'# Comment in the middle',
|
||||
'Write-Host "World"',
|
||||
'# Consecutive comments',
|
||||
'# Last line comment without line ending in the end',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'<# Comment in first line #>',
|
||||
'Write-Host "Hello"',
|
||||
'<# Comment in the middle #>',
|
||||
'Write-Host "World"',
|
||||
'<# Consecutive comments #>',
|
||||
'<# Last line comment without line ending in the end #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'can convert comment with inline comment parts inside',
|
||||
input: getWindowsLines(
|
||||
'$text+= #Comment with < inside',
|
||||
'$text+= #Comment ending with >',
|
||||
'$text+= #Comment with <# inline comment #>',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$text+= <# Comment with < inside #>',
|
||||
'$text+= <# Comment ending with > #>',
|
||||
'$text+= <# Comment with <# inline comment #> #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'can convert comment with inline comment parts around', // Pretty uncommon
|
||||
input: getWindowsLines(
|
||||
'Write-Host "hi" # Comment ending line inline comment but not one #>',
|
||||
'Write-Host "hi" #>Comment starting like inline comment end but not one',
|
||||
// Following line does not compile as valid PowerShell due to missing #> for inline comment.
|
||||
'Write-Host "hi" <#Comment starting like inline comment start but not one',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'Write-Host "hi" <# Comment ending line inline comment but not one #> #>',
|
||||
'Write-Host "hi" <# >Comment starting like inline comment end but not one #>',
|
||||
'Write-Host "hi" <<# Comment starting like inline comment start but not one #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'converts empty hash comment',
|
||||
input: getWindowsLines(
|
||||
'Write-Host "Comment without text" #',
|
||||
'Write-Host "Non-empty line"',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'Write-Host "Comment without text" <##>',
|
||||
'Write-Host "Non-empty line"',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'adds whitespaces around to match',
|
||||
input: getWindowsLines(
|
||||
'#Comment line with no whitespaces around',
|
||||
'Write-Host "Hello"#Comment in the end with no whitespaces around',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'<# Comment line with no whitespaces around #>',
|
||||
'Write-Host "Hello"<# Comment in the end with no whitespaces around #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'trims whitespaces around comment',
|
||||
input: getWindowsLines(
|
||||
'# Comment with whitespaces around ',
|
||||
'#\tComment with tabs around\t\t',
|
||||
'#\t Comment with tabs and whitespaces around \t \t',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'<# Comment with whitespaces around #>',
|
||||
'<# Comment with tabs around #>',
|
||||
'<# Comment with tabs and whitespaces around #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'does not convert block comments',
|
||||
input: getWindowsLines(
|
||||
'$text = "Hello"\t<# block comment #> + "World"',
|
||||
'$text = "Hello"\t+<#comment#>"World"',
|
||||
'<# Block comment in a line #>',
|
||||
'Write-Host "Hello world <# Block comment in the end of line #>',
|
||||
),
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$text = "Hello"\t<# block comment #> + "World"',
|
||||
'$text = "Hello"\t+<#comment#>"World"',
|
||||
'<# Block comment in a line #>',
|
||||
'Write-Host "Hello world <# Block comment in the end of line #>',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'does not process if there are no multi lines',
|
||||
input: 'Write-Host "expected" # as it is!',
|
||||
expectedOutput: 'Write-Host "expected" # as it is!',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function getNewLineCases(): IPipeTestCase[] {
|
||||
return [
|
||||
{
|
||||
name: 'no new line',
|
||||
input: 'Write-Host \'Hello, World!\'',
|
||||
expectedOutput: 'Write-Host \'Hello, World!\'',
|
||||
},
|
||||
{
|
||||
name: '\\n new line',
|
||||
input:
|
||||
'$things = Get-ChildItem C:\\Windows\\'
|
||||
+ '\nforeach ($thing in $things) {'
|
||||
+ '\nWrite-Host $thing.Name -ForegroundColor Magenta'
|
||||
+ '\n}',
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$things = Get-ChildItem C:\\Windows\\',
|
||||
'foreach ($thing in $things) {',
|
||||
'Write-Host $thing.Name -ForegroundColor Magenta',
|
||||
'}',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: '\\n double empty lines are ignored',
|
||||
input:
|
||||
'$things = Get-ChildItem C:\\Windows\\'
|
||||
+ '\n\nforeach ($thing in $things) {'
|
||||
+ '\n\nWrite-Host $thing.Name -ForegroundColor Magenta'
|
||||
+ '\n\n\n}',
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$things = Get-ChildItem C:\\Windows\\',
|
||||
'foreach ($thing in $things) {',
|
||||
'Write-Host $thing.Name -ForegroundColor Magenta',
|
||||
'}',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: '\\r new line',
|
||||
input:
|
||||
'$things = Get-ChildItem C:\\Windows\\'
|
||||
+ '\rforeach ($thing in $things) {'
|
||||
+ '\rWrite-Host $thing.Name -ForegroundColor Magenta'
|
||||
+ '\r}',
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$things = Get-ChildItem C:\\Windows\\',
|
||||
'foreach ($thing in $things) {',
|
||||
'Write-Host $thing.Name -ForegroundColor Magenta',
|
||||
'}',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: '\\r and \\n newlines combined',
|
||||
input:
|
||||
'$things = Get-ChildItem C:\\Windows\\'
|
||||
+ '\r\nforeach ($thing in $things) {'
|
||||
+ '\n\rWrite-Host $thing.Name -ForegroundColor Magenta'
|
||||
+ '\n\r}',
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$things = Get-ChildItem C:\\Windows\\',
|
||||
'foreach ($thing in $things) {',
|
||||
'Write-Host $thing.Name -ForegroundColor Magenta',
|
||||
'}',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'trims whitespaces on lines',
|
||||
input:
|
||||
' $things = Get-ChildItem C:\\Windows\\ '
|
||||
+ '\nforeach ($thing in $things) {'
|
||||
+ '\n\tWrite-Host $thing.Name -ForegroundColor Magenta'
|
||||
+ '\r \n}',
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$things = Get-ChildItem C:\\Windows\\',
|
||||
'foreach ($thing in $things) {',
|
||||
'Write-Host $thing.Name -ForegroundColor Magenta',
|
||||
'}',
|
||||
),
|
||||
},
|
||||
];
|
||||
return [
|
||||
{
|
||||
name: 'no new line',
|
||||
input: 'Write-Host \'Hello, World!\'',
|
||||
expectedOutput: 'Write-Host \'Hello, World!\'',
|
||||
},
|
||||
{
|
||||
name: '\\n new line',
|
||||
input:
|
||||
'$things = Get-ChildItem C:\\Windows\\'
|
||||
+ '\nforeach ($thing in $things) {'
|
||||
+ '\nWrite-Host $thing.Name -ForegroundColor Magenta'
|
||||
+ '\n}',
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$things = Get-ChildItem C:\\Windows\\',
|
||||
'foreach ($thing in $things) {',
|
||||
'Write-Host $thing.Name -ForegroundColor Magenta',
|
||||
'}',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: '\\n double empty lines are ignored',
|
||||
input:
|
||||
'$things = Get-ChildItem C:\\Windows\\'
|
||||
+ '\n\nforeach ($thing in $things) {'
|
||||
+ '\n\nWrite-Host $thing.Name -ForegroundColor Magenta'
|
||||
+ '\n\n\n}',
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$things = Get-ChildItem C:\\Windows\\',
|
||||
'foreach ($thing in $things) {',
|
||||
'Write-Host $thing.Name -ForegroundColor Magenta',
|
||||
'}',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: '\\r new line',
|
||||
input:
|
||||
'$things = Get-ChildItem C:\\Windows\\'
|
||||
+ '\rforeach ($thing in $things) {'
|
||||
+ '\rWrite-Host $thing.Name -ForegroundColor Magenta'
|
||||
+ '\r}',
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$things = Get-ChildItem C:\\Windows\\',
|
||||
'foreach ($thing in $things) {',
|
||||
'Write-Host $thing.Name -ForegroundColor Magenta',
|
||||
'}',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: '\\r and \\n newlines combined',
|
||||
input:
|
||||
'$things = Get-ChildItem C:\\Windows\\'
|
||||
+ '\r\nforeach ($thing in $things) {'
|
||||
+ '\n\rWrite-Host $thing.Name -ForegroundColor Magenta'
|
||||
+ '\n\r}',
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$things = Get-ChildItem C:\\Windows\\',
|
||||
'foreach ($thing in $things) {',
|
||||
'Write-Host $thing.Name -ForegroundColor Magenta',
|
||||
'}',
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'trims whitespaces on lines',
|
||||
input:
|
||||
' $things = Get-ChildItem C:\\Windows\\ '
|
||||
+ '\nforeach ($thing in $things) {'
|
||||
+ '\n\tWrite-Host $thing.Name -ForegroundColor Magenta'
|
||||
+ '\r \n}',
|
||||
expectedOutput: getSingleLinedOutput(
|
||||
'$things = Get-ChildItem C:\\Windows\\',
|
||||
'foreach ($thing in $things) {',
|
||||
'Write-Host $thing.Name -ForegroundColor Magenta',
|
||||
'}',
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function prefixTests(prefix: string, tests: IPipeTestCase[]): IPipeTestCase[] {
|
||||
return tests.map((test) => ({
|
||||
name: `[${prefix}] ${test.name}`,
|
||||
input: test.input,
|
||||
expectedOutput: test.expectedOutput,
|
||||
}));
|
||||
return tests.map((test) => ({
|
||||
name: `[${prefix}] ${test.name}`,
|
||||
input: test.input,
|
||||
expectedOutput: test.expectedOutput,
|
||||
}));
|
||||
}
|
||||
|
||||
function getWindowsLines(...lines: string[]) {
|
||||
return lines.join('\r\n');
|
||||
return lines.join('\r\n');
|
||||
}
|
||||
|
||||
function getSingleLinedOutput(...lines: string[]) {
|
||||
return lines.map((line) => line.trim()).join('; ');
|
||||
return lines.map((line) => line.trim()).join('; ');
|
||||
}
|
||||
|
||||
@@ -3,18 +3,18 @@ import { expect } from 'chai';
|
||||
import { IPipe } from '@/application/Parser/Script/Compiler/Expressions/Pipes/IPipe';
|
||||
|
||||
export interface IPipeTestCase {
|
||||
readonly name: string;
|
||||
readonly input: string;
|
||||
readonly expectedOutput: string;
|
||||
readonly name: string;
|
||||
readonly input: string;
|
||||
readonly expectedOutput: string;
|
||||
}
|
||||
|
||||
export function runPipeTests(sut: IPipe, testCases: IPipeTestCase[]) {
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// act
|
||||
const actual = sut.apply(testCase.input);
|
||||
// assert
|
||||
expect(actual).to.equal(testCase.expectedOutput);
|
||||
});
|
||||
}
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// act
|
||||
const actual = sut.apply(testCase.input);
|
||||
// assert
|
||||
expect(actual).to.equal(testCase.expectedOutput);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,110 +4,110 @@ import { PipeFactory } from '@/application/Parser/Script/Compiler/Expressions/Pi
|
||||
import { PipeStub } from '@tests/unit/stubs/PipeStub';
|
||||
|
||||
describe('PipeFactory', () => {
|
||||
describe('ctor', () => {
|
||||
it('throws when instances with same name is registered', () => {
|
||||
// arrange
|
||||
const duplicateName = 'duplicateName';
|
||||
const expectedError = `Pipe name must be unique: "${duplicateName}"`;
|
||||
const pipes = [
|
||||
new PipeStub().withName(duplicateName),
|
||||
new PipeStub().withName('uniqueName'),
|
||||
new PipeStub().withName(duplicateName),
|
||||
];
|
||||
// act
|
||||
const act = () => new PipeFactory(pipes);
|
||||
// expect
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws when a pipe is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined pipe in list';
|
||||
const pipes = [ new PipeStub(), undefined ];
|
||||
// act
|
||||
const act = () => new PipeFactory(pipes);
|
||||
// expect
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('throws when name is invalid', () => {
|
||||
// act
|
||||
const act = (invalidName: string) => new PipeFactory([ new PipeStub().withName(invalidName) ]);
|
||||
// assert
|
||||
testPipeNameValidation(act);
|
||||
});
|
||||
describe('ctor', () => {
|
||||
it('throws when instances with same name is registered', () => {
|
||||
// arrange
|
||||
const duplicateName = 'duplicateName';
|
||||
const expectedError = `Pipe name must be unique: "${duplicateName}"`;
|
||||
const pipes = [
|
||||
new PipeStub().withName(duplicateName),
|
||||
new PipeStub().withName('uniqueName'),
|
||||
new PipeStub().withName(duplicateName),
|
||||
];
|
||||
// act
|
||||
const act = () => new PipeFactory(pipes);
|
||||
// expect
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('get', () => {
|
||||
describe('throws when name is invalid', () => {
|
||||
// arrange
|
||||
const sut = new PipeFactory();
|
||||
// act
|
||||
const act = (invalidName: string) => sut.get(invalidName);
|
||||
// assert
|
||||
testPipeNameValidation(act);
|
||||
});
|
||||
it('gets registered instance when it exists', () => {
|
||||
// arrange
|
||||
const expected = new PipeStub().withName('expectedName');
|
||||
const pipes = [ expected, new PipeStub().withName('instanceToConfuse') ];
|
||||
const sut = new PipeFactory(pipes);
|
||||
// act
|
||||
const actual = sut.get(expected.name);
|
||||
// expect
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
it('throws when instance does not exist', () => {
|
||||
// arrange
|
||||
const missingName = 'missingName';
|
||||
const expectedError = `Unknown pipe: "${missingName}"`;
|
||||
const pipes = [ ];
|
||||
const sut = new PipeFactory(pipes);
|
||||
// act
|
||||
const act = () => sut.get(missingName);
|
||||
// expect
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws when a pipe is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined pipe in list';
|
||||
const pipes = [new PipeStub(), undefined];
|
||||
// act
|
||||
const act = () => new PipeFactory(pipes);
|
||||
// expect
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('throws when name is invalid', () => {
|
||||
// act
|
||||
const act = (invalidName: string) => new PipeFactory([new PipeStub().withName(invalidName)]);
|
||||
// assert
|
||||
testPipeNameValidation(act);
|
||||
});
|
||||
});
|
||||
describe('get', () => {
|
||||
describe('throws when name is invalid', () => {
|
||||
// arrange
|
||||
const sut = new PipeFactory();
|
||||
// act
|
||||
const act = (invalidName: string) => sut.get(invalidName);
|
||||
// assert
|
||||
testPipeNameValidation(act);
|
||||
});
|
||||
it('gets registered instance when it exists', () => {
|
||||
// arrange
|
||||
const expected = new PipeStub().withName('expectedName');
|
||||
const pipes = [expected, new PipeStub().withName('instanceToConfuse')];
|
||||
const sut = new PipeFactory(pipes);
|
||||
// act
|
||||
const actual = sut.get(expected.name);
|
||||
// expect
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
it('throws when instance does not exist', () => {
|
||||
// arrange
|
||||
const missingName = 'missingName';
|
||||
const expectedError = `Unknown pipe: "${missingName}"`;
|
||||
const pipes = [];
|
||||
const sut = new PipeFactory(pipes);
|
||||
// act
|
||||
const act = () => sut.get(missingName);
|
||||
// expect
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function testPipeNameValidation(testRunner: (invalidName: string) => void) {
|
||||
const testCases = [
|
||||
{
|
||||
exceptionBuilder: () => 'empty pipe name',
|
||||
values: [ null, undefined , ''],
|
||||
},
|
||||
{
|
||||
exceptionBuilder: (name: string) => `Pipe name should be camelCase: "${name}"`,
|
||||
values: [
|
||||
'PascalCase',
|
||||
'snake-case',
|
||||
'includesNumb3rs',
|
||||
'includes Whitespace',
|
||||
'noSpec\'ial',
|
||||
],
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
for (const invalidName of testCase.values) {
|
||||
it(`invalid name (${printValue(invalidName)}) throws`, () => {
|
||||
// arrange
|
||||
const expectedError = testCase.exceptionBuilder(invalidName);
|
||||
// act
|
||||
const act = () => testRunner(invalidName);
|
||||
// expect
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
}
|
||||
const testCases = [
|
||||
{
|
||||
exceptionBuilder: () => 'empty pipe name',
|
||||
values: [null, undefined, ''],
|
||||
},
|
||||
{
|
||||
exceptionBuilder: (name: string) => `Pipe name should be camelCase: "${name}"`,
|
||||
values: [
|
||||
'PascalCase',
|
||||
'snake-case',
|
||||
'includesNumb3rs',
|
||||
'includes Whitespace',
|
||||
'noSpec\'ial',
|
||||
],
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
for (const invalidName of testCase.values) {
|
||||
it(`invalid name (${printValue(invalidName)}) throws`, () => {
|
||||
// arrange
|
||||
const expectedError = testCase.exceptionBuilder(invalidName);
|
||||
// act
|
||||
const act = () => testRunner(invalidName);
|
||||
// expect
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function printValue(value: string) {
|
||||
switch (value) {
|
||||
case undefined:
|
||||
return 'undefined';
|
||||
case null:
|
||||
return 'null';
|
||||
case '':
|
||||
return 'empty';
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
switch (value) {
|
||||
case undefined:
|
||||
return 'undefined';
|
||||
case null:
|
||||
return 'null';
|
||||
case '':
|
||||
return 'empty';
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,132 +7,134 @@ import { PipeStub } from '@tests/unit/stubs/PipeStub';
|
||||
import { PipeFactoryStub } from '@tests/unit/stubs/PipeFactoryStub';
|
||||
|
||||
describe('PipelineCompiler', () => {
|
||||
describe('compile', () => {
|
||||
describe('throws for invalid arguments', () => {
|
||||
interface ITestCase {
|
||||
name: string;
|
||||
act: (test: PipelineTestRunner) => PipelineTestRunner;
|
||||
expectedError: string;
|
||||
}
|
||||
const testCases: ITestCase[] = [
|
||||
{
|
||||
name: '"value" is empty',
|
||||
act: (test) => test.withValue(''),
|
||||
expectedError: 'undefined value',
|
||||
},
|
||||
{
|
||||
name: '"value" is undefined',
|
||||
act: (test) => test.withValue(undefined),
|
||||
expectedError: 'undefined value',
|
||||
},
|
||||
{
|
||||
name: '"pipeline" is empty',
|
||||
act: (test) => test.withPipeline(''),
|
||||
expectedError: 'undefined pipeline',
|
||||
},
|
||||
{
|
||||
name: '"pipeline" is undefined',
|
||||
act: (test) => test.withPipeline(undefined),
|
||||
expectedError: 'undefined pipeline',
|
||||
},
|
||||
{
|
||||
name: '"pipeline" does not start with pipe',
|
||||
act: (test) => test.withPipeline('pipeline |'),
|
||||
expectedError: 'pipeline does not start with pipe',
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// act
|
||||
const runner = new PipelineTestRunner();
|
||||
testCase.act(runner);
|
||||
const act = () => runner.compile();
|
||||
// assert
|
||||
expect(act).to.throw(testCase.expectedError);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('compiles pipeline as expected', () => {
|
||||
const testCases = [
|
||||
{
|
||||
name: 'compiles single pipe as expected',
|
||||
pipes: [
|
||||
new PipeStub().withName('doublePrint').withApplier((value) => `${value}-${value}`),
|
||||
],
|
||||
pipeline: '| doublePrint',
|
||||
value: 'value',
|
||||
expected: 'value-value',
|
||||
},
|
||||
{
|
||||
name: 'compiles multiple pipes as expected',
|
||||
pipes: [
|
||||
new PipeStub().withName('prependLetterA').withApplier((value) => `A-${value}`),
|
||||
new PipeStub().withName('prependLetterB').withApplier((value) => `B-${value}`),
|
||||
],
|
||||
pipeline: '| prependLetterA | prependLetterB',
|
||||
value: 'value',
|
||||
expected: 'B-A-value',
|
||||
},
|
||||
{
|
||||
name: 'compiles with relaxed whitespace placing',
|
||||
pipes: [
|
||||
new PipeStub().withName('appendNumberOne').withApplier((value) => `${value}1`),
|
||||
new PipeStub().withName('appendNumberTwo').withApplier((value) => `${value}2`),
|
||||
new PipeStub().withName('appendNumberThree').withApplier((value) => `${value}3`),
|
||||
],
|
||||
pipeline: ' | appendNumberOne|appendNumberTwo| appendNumberThree',
|
||||
value: 'value',
|
||||
expected: 'value123',
|
||||
},
|
||||
{
|
||||
name: 'can reuse same pipe',
|
||||
pipes: [
|
||||
new PipeStub().withName('removeFirstChar').withApplier((value) => `${value.slice(1)}`),
|
||||
],
|
||||
pipeline: ' | removeFirstChar | removeFirstChar | removeFirstChar',
|
||||
value: 'value',
|
||||
expected: 'ue',
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// arrange
|
||||
const runner =
|
||||
new PipelineTestRunner()
|
||||
.withValue(testCase.value)
|
||||
.withPipeline(testCase.pipeline)
|
||||
.withFactory(new PipeFactoryStub().withPipes(testCase.pipes));
|
||||
// act
|
||||
const actual = runner.compile();
|
||||
// expect
|
||||
expect(actual).to.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
describe('compile', () => {
|
||||
describe('throws for invalid arguments', () => {
|
||||
interface ITestCase {
|
||||
name: string;
|
||||
act: (test: PipelineTestRunner) => PipelineTestRunner;
|
||||
expectedError: string;
|
||||
}
|
||||
const testCases: ITestCase[] = [
|
||||
{
|
||||
name: '"value" is empty',
|
||||
act: (test) => test.withValue(''),
|
||||
expectedError: 'undefined value',
|
||||
},
|
||||
{
|
||||
name: '"value" is undefined',
|
||||
act: (test) => test.withValue(undefined),
|
||||
expectedError: 'undefined value',
|
||||
},
|
||||
{
|
||||
name: '"pipeline" is empty',
|
||||
act: (test) => test.withPipeline(''),
|
||||
expectedError: 'undefined pipeline',
|
||||
},
|
||||
{
|
||||
name: '"pipeline" is undefined',
|
||||
act: (test) => test.withPipeline(undefined),
|
||||
expectedError: 'undefined pipeline',
|
||||
},
|
||||
{
|
||||
name: '"pipeline" does not start with pipe',
|
||||
act: (test) => test.withPipeline('pipeline |'),
|
||||
expectedError: 'pipeline does not start with pipe',
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// act
|
||||
const runner = new PipelineTestRunner();
|
||||
testCase.act(runner);
|
||||
const act = () => runner.compile();
|
||||
// assert
|
||||
expect(act).to.throw(testCase.expectedError);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('compiles pipeline as expected', () => {
|
||||
const testCases = [
|
||||
{
|
||||
name: 'compiles single pipe as expected',
|
||||
pipes: [
|
||||
new PipeStub().withName('doublePrint').withApplier((value) => `${value}-${value}`),
|
||||
],
|
||||
pipeline: '| doublePrint',
|
||||
value: 'value',
|
||||
expected: 'value-value',
|
||||
},
|
||||
{
|
||||
name: 'compiles multiple pipes as expected',
|
||||
pipes: [
|
||||
new PipeStub().withName('prependLetterA').withApplier((value) => `A-${value}`),
|
||||
new PipeStub().withName('prependLetterB').withApplier((value) => `B-${value}`),
|
||||
],
|
||||
pipeline: '| prependLetterA | prependLetterB',
|
||||
value: 'value',
|
||||
expected: 'B-A-value',
|
||||
},
|
||||
{
|
||||
name: 'compiles with relaxed whitespace placing',
|
||||
pipes: [
|
||||
new PipeStub().withName('appendNumberOne').withApplier((value) => `${value}1`),
|
||||
new PipeStub().withName('appendNumberTwo').withApplier((value) => `${value}2`),
|
||||
new PipeStub().withName('appendNumberThree').withApplier((value) => `${value}3`),
|
||||
],
|
||||
pipeline: ' | appendNumberOne|appendNumberTwo| appendNumberThree',
|
||||
value: 'value',
|
||||
expected: 'value123',
|
||||
},
|
||||
{
|
||||
name: 'can reuse same pipe',
|
||||
pipes: [
|
||||
new PipeStub().withName('removeFirstChar').withApplier((value) => `${value.slice(1)}`),
|
||||
],
|
||||
pipeline: ' | removeFirstChar | removeFirstChar | removeFirstChar',
|
||||
value: 'value',
|
||||
expected: 'ue',
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// arrange
|
||||
const runner = new PipelineTestRunner()
|
||||
.withValue(testCase.value)
|
||||
.withPipeline(testCase.pipeline)
|
||||
.withFactory(new PipeFactoryStub().withPipes(testCase.pipes));
|
||||
// act
|
||||
const actual = runner.compile();
|
||||
// expect
|
||||
expect(actual).to.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
class PipelineTestRunner implements IPipelineCompiler {
|
||||
private value: string = 'non-empty-value';
|
||||
private pipeline: string = '| validPipeline';
|
||||
private factory: IPipeFactory = new PipeFactoryStub();
|
||||
private value = 'non-empty-value';
|
||||
|
||||
public withValue(value: string) {
|
||||
this.value = value;
|
||||
return this;
|
||||
}
|
||||
public withPipeline(pipeline: string) {
|
||||
this.pipeline = pipeline;
|
||||
return this;
|
||||
}
|
||||
public withFactory(factory: IPipeFactory) {
|
||||
this.factory = factory;
|
||||
return this;
|
||||
}
|
||||
private pipeline = '| validPipeline';
|
||||
|
||||
public compile(): string {
|
||||
const sut = new PipelineCompiler(this.factory);
|
||||
return sut.compile(this.value, this.pipeline);
|
||||
}
|
||||
private factory: IPipeFactory = new PipeFactoryStub();
|
||||
|
||||
public withValue(value: string) {
|
||||
this.value = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withPipeline(pipeline: string) {
|
||||
this.pipeline = pipeline;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withFactory(factory: IPipeFactory) {
|
||||
this.factory = factory;
|
||||
return this;
|
||||
}
|
||||
|
||||
public compile(): string {
|
||||
const sut = new PipelineCompiler(this.factory);
|
||||
return sut.compile(this.value, this.pipeline);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,64 +4,64 @@ import { ExpressionPosition } from '@/application/Parser/Script/Compiler/Express
|
||||
import { SyntaxParserTestsRunner } from './SyntaxParserTestsRunner';
|
||||
|
||||
describe('ParameterSubstitutionParser', () => {
|
||||
const sut = new ParameterSubstitutionParser();
|
||||
const runner = new SyntaxParserTestsRunner(sut);
|
||||
describe('finds as expected', () => {
|
||||
runner.expectPosition(
|
||||
{
|
||||
name: 'single parameter',
|
||||
code: '{{ $parameter }}!',
|
||||
expected: [ new ExpressionPosition(0, 16) ],
|
||||
},
|
||||
{
|
||||
name: 'different parameters',
|
||||
code: 'He{{ $firstParameter }} {{ $secondParameter }}!!',
|
||||
expected: [ new ExpressionPosition(2, 23), new ExpressionPosition(24, 46) ],
|
||||
},
|
||||
{
|
||||
name: 'tolerates lack of spaces around brackets',
|
||||
code: 'He{{$firstParameter}}!!',
|
||||
expected: [new ExpressionPosition(2, 21) ],
|
||||
},
|
||||
{
|
||||
name: 'does not tolerate space after dollar sign',
|
||||
code: 'He{{ $ firstParameter }}!!',
|
||||
expected: [ ],
|
||||
},
|
||||
);
|
||||
});
|
||||
describe('evaluates as expected', () => {
|
||||
runner.expectResults(
|
||||
{
|
||||
name: 'single parameter',
|
||||
code: '{{ $parameter }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'Hello world'),
|
||||
expected: [ 'Hello world' ],
|
||||
},
|
||||
{
|
||||
name: 'different parameters',
|
||||
code: '{{ $firstParameter }} {{ $secondParameter }}!',
|
||||
args: (args) => args
|
||||
.withArgument('firstParameter', 'Hello')
|
||||
.withArgument('secondParameter', 'World'),
|
||||
expected: [ 'Hello', 'World' ],
|
||||
},
|
||||
{
|
||||
name: 'same parameters used twice',
|
||||
code: '{{ $letterH }}e{{ $letterL }}{{ $letterL }}o Wor{{ $letterL }}d!',
|
||||
args: (args) => args
|
||||
.withArgument('letterL', 'l')
|
||||
.withArgument('letterH', 'H'),
|
||||
expected: [ 'H', 'l', 'l', 'l' ],
|
||||
},
|
||||
);
|
||||
});
|
||||
describe('compiles pipes as expected', () => {
|
||||
runner.expectPipeHits({
|
||||
codeBuilder: (pipeline) => `{{ $argument${pipeline}}}`,
|
||||
parameterName: 'argument',
|
||||
parameterValue: 'value',
|
||||
});
|
||||
const sut = new ParameterSubstitutionParser();
|
||||
const runner = new SyntaxParserTestsRunner(sut);
|
||||
describe('finds as expected', () => {
|
||||
runner.expectPosition(
|
||||
{
|
||||
name: 'single parameter',
|
||||
code: '{{ $parameter }}!',
|
||||
expected: [new ExpressionPosition(0, 16)],
|
||||
},
|
||||
{
|
||||
name: 'different parameters',
|
||||
code: 'He{{ $firstParameter }} {{ $secondParameter }}!!',
|
||||
expected: [new ExpressionPosition(2, 23), new ExpressionPosition(24, 46)],
|
||||
},
|
||||
{
|
||||
name: 'tolerates lack of spaces around brackets',
|
||||
code: 'He{{$firstParameter}}!!',
|
||||
expected: [new ExpressionPosition(2, 21)],
|
||||
},
|
||||
{
|
||||
name: 'does not tolerate space after dollar sign',
|
||||
code: 'He{{ $ firstParameter }}!!',
|
||||
expected: [],
|
||||
},
|
||||
);
|
||||
});
|
||||
describe('evaluates as expected', () => {
|
||||
runner.expectResults(
|
||||
{
|
||||
name: 'single parameter',
|
||||
code: '{{ $parameter }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'Hello world'),
|
||||
expected: ['Hello world'],
|
||||
},
|
||||
{
|
||||
name: 'different parameters',
|
||||
code: '{{ $firstParameter }} {{ $secondParameter }}!',
|
||||
args: (args) => args
|
||||
.withArgument('firstParameter', 'Hello')
|
||||
.withArgument('secondParameter', 'World'),
|
||||
expected: ['Hello', 'World'],
|
||||
},
|
||||
{
|
||||
name: 'same parameters used twice',
|
||||
code: '{{ $letterH }}e{{ $letterL }}{{ $letterL }}o Wor{{ $letterL }}d!',
|
||||
args: (args) => args
|
||||
.withArgument('letterL', 'l')
|
||||
.withArgument('letterH', 'H'),
|
||||
expected: ['H', 'l', 'l', 'l'],
|
||||
},
|
||||
);
|
||||
});
|
||||
describe('compiles pipes as expected', () => {
|
||||
runner.expectPipeHits({
|
||||
codeBuilder: (pipeline) => `{{ $argument${pipeline}}}`,
|
||||
parameterName: 'argument',
|
||||
parameterValue: 'value',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,116 +7,121 @@ import { ExpressionEvaluationContextStub } from '@tests/unit/stubs/ExpressionEva
|
||||
import { PipelineCompilerStub } from '@tests/unit/stubs/PipelineCompilerStub';
|
||||
|
||||
export class SyntaxParserTestsRunner {
|
||||
constructor(private readonly sut: IExpressionParser) {
|
||||
constructor(private readonly sut: IExpressionParser) {
|
||||
}
|
||||
|
||||
public expectPosition(...testCases: IExpectPositionTestCase[]) {
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// act
|
||||
const expressions = this.sut.findExpressions(testCase.code);
|
||||
// assert
|
||||
const actual = expressions.map((e) => e.position);
|
||||
expect(actual).to.deep.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
public expectPosition(...testCases: IExpectPositionTestCase[]) {
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// act
|
||||
const expressions = this.sut.findExpressions(testCase.code);
|
||||
// assert
|
||||
const actual = expressions.map((e) => e.position);
|
||||
expect(actual).to.deep.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
return this;
|
||||
return this;
|
||||
}
|
||||
|
||||
public expectResults(...testCases: IExpectResultTestCase[]) {
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// arrange
|
||||
const args = testCase.args(new FunctionCallArgumentCollectionStub());
|
||||
const context = new ExpressionEvaluationContextStub()
|
||||
.withArgs(args);
|
||||
// act
|
||||
const expressions = this.sut.findExpressions(testCase.code);
|
||||
// assert
|
||||
const actual = expressions.map((e) => e.evaluate(context));
|
||||
expect(actual).to.deep.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
public expectResults(...testCases: IExpectResultTestCase[]) {
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// arrange
|
||||
const args = testCase.args(new FunctionCallArgumentCollectionStub());
|
||||
const context = new ExpressionEvaluationContextStub()
|
||||
.withArgs(args);
|
||||
// act
|
||||
const expressions = this.sut.findExpressions(testCase.code);
|
||||
// assert
|
||||
const actual = expressions.map((e) => e.evaluate(context));
|
||||
expect(actual).to.deep.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
return this;
|
||||
return this;
|
||||
}
|
||||
|
||||
public expectPipeHits(data: IExpectPipeHitTestData) {
|
||||
for (const validPipePart of PipeTestCases.ValidValues) {
|
||||
this.expectHitPipePart(validPipePart, data);
|
||||
}
|
||||
public expectPipeHits(data: IExpectPipeHitTestData) {
|
||||
for (const validPipePart of PipeTestCases.ValidValues) {
|
||||
this.expectHitPipePart(validPipePart, data);
|
||||
}
|
||||
for (const invalidPipePart of PipeTestCases.InvalidValues) {
|
||||
this.expectMissPipePart(invalidPipePart, data);
|
||||
}
|
||||
}
|
||||
private expectHitPipePart(pipeline: string, data: IExpectPipeHitTestData) {
|
||||
it(`"${pipeline}" hits`, () => {
|
||||
// arrange
|
||||
const expectedPipePart = pipeline.trim();
|
||||
const code = data.codeBuilder(pipeline);
|
||||
const args = new FunctionCallArgumentCollectionStub()
|
||||
.withArgument(data.parameterName, data.parameterValue);
|
||||
const pipelineCompiler = new PipelineCompilerStub();
|
||||
const context = new ExpressionEvaluationContextStub()
|
||||
.withPipelineCompiler(pipelineCompiler)
|
||||
.withArgs(args);
|
||||
// act
|
||||
const expressions = this.sut.findExpressions(code);
|
||||
expressions[0].evaluate(context);
|
||||
// assert
|
||||
expect(expressions).has.lengthOf(1);
|
||||
expect(pipelineCompiler.compileHistory).has.lengthOf(1);
|
||||
const actualPipeNames = pipelineCompiler.compileHistory[0].pipeline;
|
||||
const actualValue = pipelineCompiler.compileHistory[0].value;
|
||||
expect(actualPipeNames).to.equal(expectedPipePart);
|
||||
expect(actualValue).to.equal(data.parameterValue);
|
||||
});
|
||||
}
|
||||
private expectMissPipePart(pipeline: string, data: IExpectPipeHitTestData) {
|
||||
it(`"${pipeline}" misses`, () => {
|
||||
// arrange
|
||||
const args = new FunctionCallArgumentCollectionStub()
|
||||
.withArgument(data.parameterName, data.parameterValue);
|
||||
const pipelineCompiler = new PipelineCompilerStub();
|
||||
const context = new ExpressionEvaluationContextStub()
|
||||
.withPipelineCompiler(pipelineCompiler)
|
||||
.withArgs(args);
|
||||
const code = data.codeBuilder(pipeline);
|
||||
// act
|
||||
const expressions = this.sut.findExpressions(code);
|
||||
expressions[0]?.evaluate(context); // Because an expression may include another with pipes
|
||||
// assert
|
||||
expect(pipelineCompiler.compileHistory).has.lengthOf(0);
|
||||
});
|
||||
for (const invalidPipePart of PipeTestCases.InvalidValues) {
|
||||
this.expectMissPipePart(invalidPipePart, data);
|
||||
}
|
||||
}
|
||||
|
||||
private expectHitPipePart(pipeline: string, data: IExpectPipeHitTestData) {
|
||||
it(`"${pipeline}" hits`, () => {
|
||||
// arrange
|
||||
const expectedPipePart = pipeline.trim();
|
||||
const code = data.codeBuilder(pipeline);
|
||||
const args = new FunctionCallArgumentCollectionStub()
|
||||
.withArgument(data.parameterName, data.parameterValue);
|
||||
const pipelineCompiler = new PipelineCompilerStub();
|
||||
const context = new ExpressionEvaluationContextStub()
|
||||
.withPipelineCompiler(pipelineCompiler)
|
||||
.withArgs(args);
|
||||
// act
|
||||
const expressions = this.sut.findExpressions(code);
|
||||
expressions[0].evaluate(context);
|
||||
// assert
|
||||
expect(expressions).has.lengthOf(1);
|
||||
expect(pipelineCompiler.compileHistory).has.lengthOf(1);
|
||||
const actualPipeNames = pipelineCompiler.compileHistory[0].pipeline;
|
||||
const actualValue = pipelineCompiler.compileHistory[0].value;
|
||||
expect(actualPipeNames).to.equal(expectedPipePart);
|
||||
expect(actualValue).to.equal(data.parameterValue);
|
||||
});
|
||||
}
|
||||
|
||||
private expectMissPipePart(pipeline: string, data: IExpectPipeHitTestData) {
|
||||
it(`"${pipeline}" misses`, () => {
|
||||
// arrange
|
||||
const args = new FunctionCallArgumentCollectionStub()
|
||||
.withArgument(data.parameterName, data.parameterValue);
|
||||
const pipelineCompiler = new PipelineCompilerStub();
|
||||
const context = new ExpressionEvaluationContextStub()
|
||||
.withPipelineCompiler(pipelineCompiler)
|
||||
.withArgs(args);
|
||||
const code = data.codeBuilder(pipeline);
|
||||
// act
|
||||
const expressions = this.sut.findExpressions(code);
|
||||
expressions[0]?.evaluate(context); // Because an expression may include another with pipes
|
||||
// assert
|
||||
expect(pipelineCompiler.compileHistory).has.lengthOf(0);
|
||||
});
|
||||
}
|
||||
}
|
||||
interface IExpectResultTestCase {
|
||||
name: string;
|
||||
code: string;
|
||||
args: (builder: FunctionCallArgumentCollectionStub) => FunctionCallArgumentCollectionStub;
|
||||
expected: readonly string[];
|
||||
name: string;
|
||||
code: string;
|
||||
args: (builder: FunctionCallArgumentCollectionStub) => FunctionCallArgumentCollectionStub;
|
||||
expected: readonly string[];
|
||||
}
|
||||
|
||||
interface IExpectPositionTestCase {
|
||||
name: string;
|
||||
code: string;
|
||||
expected: readonly ExpressionPosition[];
|
||||
name: string;
|
||||
code: string;
|
||||
expected: readonly ExpressionPosition[];
|
||||
}
|
||||
|
||||
interface IExpectPipeHitTestData {
|
||||
codeBuilder: (pipeline: string) => string;
|
||||
parameterName: string;
|
||||
parameterValue: string;
|
||||
codeBuilder: (pipeline: string) => string;
|
||||
parameterName: string;
|
||||
parameterValue: string;
|
||||
}
|
||||
|
||||
const PipeTestCases = {
|
||||
ValidValues: [
|
||||
// Single pipe with different whitespace combinations
|
||||
' | pipe1', ' |pipe1', '|pipe1', ' |pipe1', ' | pipe1',
|
||||
ValidValues: [
|
||||
// Single pipe with different whitespace combinations
|
||||
' | pipe1', ' |pipe1', '|pipe1', ' |pipe1', ' | pipe1',
|
||||
|
||||
// Double pipes with different whitespace combinations
|
||||
' | pipe1 | pipe2', '| pipe1|pipe2', '|pipe1|pipe2', ' |pipe1 |pipe2', '| pipe1 | pipe2| pipe3 |pipe4',
|
||||
// Double pipes with different whitespace combinations
|
||||
' | pipe1 | pipe2', '| pipe1|pipe2', '|pipe1|pipe2', ' |pipe1 |pipe2', '| pipe1 | pipe2| pipe3 |pipe4',
|
||||
|
||||
// Wrong cases, but should match anyway and let pipelineCompiler throw errors
|
||||
'| pip€', '| pip{e} ',
|
||||
],
|
||||
InvalidValues: [
|
||||
' pipe1 |pipe2', ' pipe1',
|
||||
],
|
||||
// Wrong cases, but should match anyway and let pipelineCompiler throw errors
|
||||
'| pip€', '| pip{e} ',
|
||||
],
|
||||
InvalidValues: [
|
||||
' pipe1 |pipe2', ' pipe1',
|
||||
],
|
||||
};
|
||||
|
||||
@@ -4,180 +4,178 @@ import { WithParser } from '@/application/Parser/Script/Compiler/Expressions/Syn
|
||||
import { SyntaxParserTestsRunner } from './SyntaxParserTestsRunner';
|
||||
|
||||
describe('WithParser', () => {
|
||||
const sut = new WithParser();
|
||||
const runner = new SyntaxParserTestsRunner(sut);
|
||||
describe('finds as expected', () => {
|
||||
runner.expectPosition(
|
||||
|
||||
{
|
||||
name: 'when no scope is not used',
|
||||
code: 'hello {{ with $parameter }}no usage{{ end }} here',
|
||||
expected: [ new ExpressionPosition(6, 44) ],
|
||||
},
|
||||
{
|
||||
name: 'when scope is used',
|
||||
code: 'used here ({{ with $parameter }}value: {{.}}{{ end }})',
|
||||
expected: [ new ExpressionPosition(11, 53) ],
|
||||
},
|
||||
{
|
||||
name: 'when used twice',
|
||||
code: 'first: {{ with $parameter }}value: {{ . }}{{ end }}, second: {{ with $parameter }}no usage{{ end }}',
|
||||
expected: [ new ExpressionPosition(7, 51), new ExpressionPosition(61, 99) ],
|
||||
},
|
||||
{
|
||||
name: 'tolerate lack of whitespaces',
|
||||
code: 'no whitespaces {{with $parameter}}value: {{ . }}{{end}}',
|
||||
expected: [ new ExpressionPosition(15, 55) ],
|
||||
},
|
||||
);
|
||||
const sut = new WithParser();
|
||||
const runner = new SyntaxParserTestsRunner(sut);
|
||||
describe('finds as expected', () => {
|
||||
runner.expectPosition(
|
||||
{
|
||||
name: 'when no scope is not used',
|
||||
code: 'hello {{ with $parameter }}no usage{{ end }} here',
|
||||
expected: [new ExpressionPosition(6, 44)],
|
||||
},
|
||||
{
|
||||
name: 'when scope is used',
|
||||
code: 'used here ({{ with $parameter }}value: {{.}}{{ end }})',
|
||||
expected: [new ExpressionPosition(11, 53)],
|
||||
},
|
||||
{
|
||||
name: 'when used twice',
|
||||
code: 'first: {{ with $parameter }}value: {{ . }}{{ end }}, second: {{ with $parameter }}no usage{{ end }}',
|
||||
expected: [new ExpressionPosition(7, 51), new ExpressionPosition(61, 99)],
|
||||
},
|
||||
{
|
||||
name: 'tolerate lack of whitespaces',
|
||||
code: 'no whitespaces {{with $parameter}}value: {{ . }}{{end}}',
|
||||
expected: [new ExpressionPosition(15, 55)],
|
||||
},
|
||||
);
|
||||
});
|
||||
describe('ignores when syntax is wrong', () => {
|
||||
describe('ignores expression if "with" syntax is wrong', () => {
|
||||
runner.expectPosition(
|
||||
{
|
||||
name: 'does not tolerate whitespace after with',
|
||||
code: '{{with $ parameter}}value: {{ . }}{{ end }}',
|
||||
expected: [],
|
||||
},
|
||||
{
|
||||
name: 'does not tolerate whitespace before dollar',
|
||||
code: '{{ with$parameter}}value: {{ . }}{{ end }}',
|
||||
expected: [],
|
||||
},
|
||||
{
|
||||
name: 'wrong text at scope end',
|
||||
code: '{{ with$parameter}}value: {{ . }}{{ fin }}',
|
||||
expected: [],
|
||||
},
|
||||
{
|
||||
name: 'wrong text at expression start',
|
||||
code: '{{ when $parameter}}value: {{ . }}{{ end }}',
|
||||
expected: [],
|
||||
},
|
||||
);
|
||||
});
|
||||
describe('ignores when syntax is wrong', () => {
|
||||
describe('ignores expression if "with" syntax is wrong', () => {
|
||||
runner.expectPosition(
|
||||
{
|
||||
name: 'does not tolerate whitespace after with',
|
||||
code: '{{with $ parameter}}value: {{ . }}{{ end }}',
|
||||
expected: [ ],
|
||||
},
|
||||
{
|
||||
name: 'does not tolerate whitespace before dollar',
|
||||
code: '{{ with$parameter}}value: {{ . }}{{ end }}',
|
||||
expected: [ ],
|
||||
},
|
||||
{
|
||||
name: 'wrong text at scope end',
|
||||
code: '{{ with$parameter}}value: {{ . }}{{ fin }}',
|
||||
expected: [ ],
|
||||
},
|
||||
{
|
||||
name: 'wrong text at expression start',
|
||||
code: '{{ when $parameter}}value: {{ . }}{{ end }}',
|
||||
expected: [ ],
|
||||
},
|
||||
);
|
||||
|
||||
});
|
||||
describe('does not render argument if substitution syntax is wrong', () => {
|
||||
runner.expectResults(
|
||||
{
|
||||
name: 'comma used instead of dot',
|
||||
code: '{{ with $parameter }}Hello {{ , }}{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'world!'),
|
||||
expected: [ 'Hello {{ , }}' ],
|
||||
},
|
||||
{
|
||||
name: 'single brackets instead of double',
|
||||
code: '{{ with $parameter }}Hello { . }{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'world!'),
|
||||
expected: [ 'Hello { . }' ],
|
||||
},
|
||||
{
|
||||
name: 'double dots instead of single',
|
||||
code: '{{ with $parameter }}Hello {{ .. }}{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'world!'),
|
||||
expected: [ 'Hello {{ .. }}' ],
|
||||
},
|
||||
);
|
||||
});
|
||||
describe('does not render argument if substitution syntax is wrong', () => {
|
||||
runner.expectResults(
|
||||
{
|
||||
name: 'comma used instead of dot',
|
||||
code: '{{ with $parameter }}Hello {{ , }}{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'world!'),
|
||||
expected: ['Hello {{ , }}'],
|
||||
},
|
||||
{
|
||||
name: 'single brackets instead of double',
|
||||
code: '{{ with $parameter }}Hello { . }{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'world!'),
|
||||
expected: ['Hello { . }'],
|
||||
},
|
||||
{
|
||||
name: 'double dots instead of single',
|
||||
code: '{{ with $parameter }}Hello {{ .. }}{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'world!'),
|
||||
expected: ['Hello {{ .. }}'],
|
||||
},
|
||||
);
|
||||
});
|
||||
describe('renders scope conditionally', () => {
|
||||
describe('does not render scope if argument is undefined', () => {
|
||||
runner.expectResults(
|
||||
{
|
||||
name: 'does not render when value is undefined',
|
||||
code: '{{ with $parameter }}dark{{ end }} ',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', undefined),
|
||||
expected: [ '' ],
|
||||
},
|
||||
{
|
||||
name: 'does not render when value is empty',
|
||||
code: '{{ with $parameter }}dark {{.}}{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', ''),
|
||||
expected: [ '' ],
|
||||
},
|
||||
{
|
||||
name: 'does not render when argument is not provided',
|
||||
code: '{{ with $parameter }}dark{{ end }}',
|
||||
args: (args) => args,
|
||||
expected: [ '' ],
|
||||
},
|
||||
);
|
||||
});
|
||||
describe('render scope when variable has value', () => {
|
||||
runner.expectResults(
|
||||
{
|
||||
name: 'renders scope even if value is not used',
|
||||
code: '{{ with $parameter }}Hello world!{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'Hello'),
|
||||
expected: [ 'Hello world!' ],
|
||||
},
|
||||
{
|
||||
name: 'renders value when it has value',
|
||||
code: '{{ with $parameter }}{{ . }} world!{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'Hello'),
|
||||
expected: [ 'Hello world!' ],
|
||||
},
|
||||
{
|
||||
name: 'renders value when whitespaces around brackets are missing',
|
||||
code: '{{ with $parameter }}{{.}} world!{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'Hello'),
|
||||
expected: [ 'Hello world!' ],
|
||||
},
|
||||
{
|
||||
name: 'renders value multiple times when it\'s used multiple times',
|
||||
code: '{{ with $letterL }}He{{ . }}{{ . }}o wor{{ . }}d!{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('letterL', 'l'),
|
||||
expected: [ 'Hello world!' ],
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('renders scope conditionally', () => {
|
||||
describe('does not render scope if argument is undefined', () => {
|
||||
runner.expectResults(
|
||||
{
|
||||
name: 'does not render when value is undefined',
|
||||
code: '{{ with $parameter }}dark{{ end }} ',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', undefined),
|
||||
expected: [''],
|
||||
},
|
||||
{
|
||||
name: 'does not render when value is empty',
|
||||
code: '{{ with $parameter }}dark {{.}}{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', ''),
|
||||
expected: [''],
|
||||
},
|
||||
{
|
||||
name: 'does not render when argument is not provided',
|
||||
code: '{{ with $parameter }}dark{{ end }}',
|
||||
args: (args) => args,
|
||||
expected: [''],
|
||||
},
|
||||
);
|
||||
});
|
||||
describe('ignores trailing and leading whitespaces and newlines inside scope', () => {
|
||||
runner.expectResults(
|
||||
{
|
||||
name: 'does not render trailing whitespace after value',
|
||||
code: '{{ with $parameter }}{{ . }}! {{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'Hello world'),
|
||||
expected: [ 'Hello world!' ],
|
||||
},
|
||||
{
|
||||
name: 'does not render trailing newline after value',
|
||||
code: '{{ with $parameter }}{{ . }}!\r\n{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'Hello world'),
|
||||
expected: [ 'Hello world!' ],
|
||||
},
|
||||
{
|
||||
name: 'does not render leading newline before value',
|
||||
code: '{{ with $parameter }}\r\n{{ . }}!{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'Hello world'),
|
||||
expected: [ 'Hello world!' ],
|
||||
},
|
||||
{
|
||||
name: 'does not render leading whitespace before value',
|
||||
code: '{{ with $parameter }} {{ . }}!{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'Hello world'),
|
||||
expected: [ 'Hello world!' ],
|
||||
},
|
||||
);
|
||||
describe('render scope when variable has value', () => {
|
||||
runner.expectResults(
|
||||
{
|
||||
name: 'renders scope even if value is not used',
|
||||
code: '{{ with $parameter }}Hello world!{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'Hello'),
|
||||
expected: ['Hello world!'],
|
||||
},
|
||||
{
|
||||
name: 'renders value when it has value',
|
||||
code: '{{ with $parameter }}{{ . }} world!{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'Hello'),
|
||||
expected: ['Hello world!'],
|
||||
},
|
||||
{
|
||||
name: 'renders value when whitespaces around brackets are missing',
|
||||
code: '{{ with $parameter }}{{.}} world!{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'Hello'),
|
||||
expected: ['Hello world!'],
|
||||
},
|
||||
{
|
||||
name: 'renders value multiple times when it\'s used multiple times',
|
||||
code: '{{ with $letterL }}He{{ . }}{{ . }}o wor{{ . }}d!{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('letterL', 'l'),
|
||||
expected: ['Hello world!'],
|
||||
},
|
||||
);
|
||||
});
|
||||
describe('compiles pipes in scope as expected', () => {
|
||||
runner.expectPipeHits({
|
||||
codeBuilder: (pipeline) => `{{ with $argument }} {{ .${pipeline}}} {{ end }}`,
|
||||
parameterName: 'argument',
|
||||
parameterValue: 'value',
|
||||
});
|
||||
});
|
||||
describe('ignores trailing and leading whitespaces and newlines inside scope', () => {
|
||||
runner.expectResults(
|
||||
{
|
||||
name: 'does not render trailing whitespace after value',
|
||||
code: '{{ with $parameter }}{{ . }}! {{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'Hello world'),
|
||||
expected: ['Hello world!'],
|
||||
},
|
||||
{
|
||||
name: 'does not render trailing newline after value',
|
||||
code: '{{ with $parameter }}{{ . }}!\r\n{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'Hello world'),
|
||||
expected: ['Hello world!'],
|
||||
},
|
||||
{
|
||||
name: 'does not render leading newline before value',
|
||||
code: '{{ with $parameter }}\r\n{{ . }}!{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'Hello world'),
|
||||
expected: ['Hello world!'],
|
||||
},
|
||||
{
|
||||
name: 'does not render leading whitespace before value',
|
||||
code: '{{ with $parameter }} {{ . }}!{{ end }}',
|
||||
args: (args) => args
|
||||
.withArgument('parameter', 'Hello world'),
|
||||
expected: ['Hello world!'],
|
||||
},
|
||||
);
|
||||
});
|
||||
describe('compiles pipes in scope as expected', () => {
|
||||
runner.expectPipeHits({
|
||||
codeBuilder: (pipeline) => `{{ with $argument }} {{ .${pipeline}}} {{ end }}`,
|
||||
parameterName: 'argument',
|
||||
parameterValue: 'value',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -2,55 +2,55 @@ import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
|
||||
export function testParameterName(action: (parameterName: string) => string) {
|
||||
describe('name', () => {
|
||||
describe('sets as expected', () => {
|
||||
// arrange
|
||||
const expectedValues = [
|
||||
'lowercase',
|
||||
'onlyLetters',
|
||||
'l3tt3rsW1thNumb3rs',
|
||||
];
|
||||
for (const expected of expectedValues) {
|
||||
it(expected, () => {
|
||||
// act
|
||||
const value = action(expected);
|
||||
// assert
|
||||
expect(value).to.equal(expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('throws if invalid', () => {
|
||||
// arrange
|
||||
const testCases = [
|
||||
{
|
||||
name: 'undefined',
|
||||
value: undefined,
|
||||
expectedError: 'undefined parameter name',
|
||||
},
|
||||
{
|
||||
name: 'empty',
|
||||
value: '',
|
||||
expectedError: 'undefined parameter name',
|
||||
},
|
||||
{
|
||||
name: 'has @',
|
||||
value: 'b@d',
|
||||
expectedError: 'parameter name must be alphanumeric but it was "b@d"',
|
||||
},
|
||||
{
|
||||
name: 'has {',
|
||||
value: 'b{a}d',
|
||||
expectedError: 'parameter name must be alphanumeric but it was "b{a}d"',
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// act
|
||||
const act = () => action(testCase.value);
|
||||
// assert
|
||||
expect(act).to.throw(testCase.expectedError);
|
||||
});
|
||||
}
|
||||
describe('name', () => {
|
||||
describe('sets as expected', () => {
|
||||
// arrange
|
||||
const expectedValues = [
|
||||
'lowercase',
|
||||
'onlyLetters',
|
||||
'l3tt3rsW1thNumb3rs',
|
||||
];
|
||||
for (const expected of expectedValues) {
|
||||
it(expected, () => {
|
||||
// act
|
||||
const value = action(expected);
|
||||
// assert
|
||||
expect(value).to.equal(expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('throws if invalid', () => {
|
||||
// arrange
|
||||
const testCases = [
|
||||
{
|
||||
name: 'undefined',
|
||||
value: undefined,
|
||||
expectedError: 'undefined parameter name',
|
||||
},
|
||||
{
|
||||
name: 'empty',
|
||||
value: '',
|
||||
expectedError: 'undefined parameter name',
|
||||
},
|
||||
{
|
||||
name: 'has @',
|
||||
value: 'b@d',
|
||||
expectedError: 'parameter name must be alphanumeric but it was "b@d"',
|
||||
},
|
||||
{
|
||||
name: 'has {',
|
||||
value: 'b{a}d',
|
||||
expectedError: 'parameter name must be alphanumeric but it was "b{a}d"',
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// act
|
||||
const act = () => action(testCase.value);
|
||||
// assert
|
||||
expect(act).to.throw(testCase.expectedError);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,198 +16,217 @@ import { parseFunctionCalls } from '@/application/Parser/Script/Compiler/Functio
|
||||
import { FunctionCallDataStub } from '@tests/unit/stubs/FunctionCallDataStub';
|
||||
|
||||
describe('ScriptCompiler', () => {
|
||||
describe('ctor', () => {
|
||||
it('throws if syntax is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = `undefined syntax`;
|
||||
// act
|
||||
const act = () => new ScriptCompilerBuilder()
|
||||
.withSomeFunctions()
|
||||
.withSyntax(undefined)
|
||||
.build();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('ctor', () => {
|
||||
it('throws if syntax is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined syntax';
|
||||
// act
|
||||
const act = () => new ScriptCompilerBuilder()
|
||||
.withSomeFunctions()
|
||||
.withSyntax(undefined)
|
||||
.build();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('canCompile', () => {
|
||||
it('throws if script is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined script';
|
||||
const argument = undefined;
|
||||
const builder = new ScriptCompilerBuilder()
|
||||
.withEmptyFunctions()
|
||||
.build();
|
||||
// act
|
||||
const act = () => builder.canCompile(argument);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('returns true if "call" is defined', () => {
|
||||
// arrange
|
||||
const sut = new ScriptCompilerBuilder()
|
||||
.withEmptyFunctions()
|
||||
.build();
|
||||
const script = ScriptDataStub.createWithCall();
|
||||
// act
|
||||
const actual = sut.canCompile(script);
|
||||
// assert
|
||||
expect(actual).to.equal(true);
|
||||
});
|
||||
it('returns false if "call" is undefined', () => {
|
||||
// arrange
|
||||
const sut = new ScriptCompilerBuilder()
|
||||
.withEmptyFunctions()
|
||||
.build();
|
||||
const script = ScriptDataStub.createWithCode();
|
||||
// act
|
||||
const actual = sut.canCompile(script);
|
||||
// assert
|
||||
expect(actual).to.equal(false);
|
||||
});
|
||||
});
|
||||
describe('canCompile', () => {
|
||||
it('throws if script is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined script';
|
||||
const argument = undefined;
|
||||
const builder = new ScriptCompilerBuilder()
|
||||
.withEmptyFunctions()
|
||||
.build();
|
||||
// act
|
||||
const act = () => builder.canCompile(argument);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
describe('compile', () => {
|
||||
it('throws if script is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined script';
|
||||
const argument = undefined;
|
||||
const builder = new ScriptCompilerBuilder()
|
||||
.withEmptyFunctions()
|
||||
.build();
|
||||
// act
|
||||
const act = () => builder.compile(argument);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('returns code as expected', () => {
|
||||
// arrange
|
||||
const expected: ICompiledCode = {
|
||||
code: 'expected-code',
|
||||
revertCode: 'expected-revert-code',
|
||||
};
|
||||
const call = new FunctionCallDataStub();
|
||||
const script = ScriptDataStub.createWithCall(call);
|
||||
const functions = [ FunctionDataStub.createWithCode().withName('existing-func') ];
|
||||
const compiledFunctions = new SharedFunctionCollectionStub();
|
||||
const functionParserMock = new SharedFunctionsParserStub();
|
||||
functionParserMock.setup(functions, compiledFunctions);
|
||||
const callCompilerMock = new FunctionCallCompilerStub();
|
||||
callCompilerMock.setup(parseFunctionCalls(call), compiledFunctions, expected);
|
||||
const sut = new ScriptCompilerBuilder()
|
||||
.withFunctions(...functions)
|
||||
.withSharedFunctionsParser(functionParserMock)
|
||||
.withFunctionCallCompiler(callCompilerMock)
|
||||
.build();
|
||||
// act
|
||||
const code = sut.compile(script);
|
||||
// assert
|
||||
expect(code.execute).to.equal(expected.code);
|
||||
expect(code.revert).to.equal(expected.revertCode);
|
||||
});
|
||||
it('creates with expected syntax', () => {
|
||||
// arrange
|
||||
let isUsed = false;
|
||||
const syntax: ILanguageSyntax = {
|
||||
get commentDelimiters() {
|
||||
isUsed = true;
|
||||
return [];
|
||||
},
|
||||
get commonCodeParts() {
|
||||
isUsed = true;
|
||||
return [];
|
||||
},
|
||||
};
|
||||
const sut = new ScriptCompilerBuilder()
|
||||
.withSomeFunctions()
|
||||
.withSyntax(syntax)
|
||||
.build();
|
||||
const scriptData = ScriptDataStub.createWithCall();
|
||||
// act
|
||||
sut.compile(scriptData);
|
||||
// assert
|
||||
expect(isUsed).to.equal(true);
|
||||
});
|
||||
it('rethrows error with script name', () => {
|
||||
// arrange
|
||||
const scriptName = 'scriptName';
|
||||
const innerError = 'innerError';
|
||||
const expectedError = `Script "${scriptName}" ${innerError}`;
|
||||
const callCompiler: IFunctionCallCompiler = {
|
||||
compileCall: () => { throw new Error(innerError); },
|
||||
};
|
||||
const scriptData = ScriptDataStub.createWithCall()
|
||||
.withName(scriptName);
|
||||
const sut = new ScriptCompilerBuilder()
|
||||
.withSomeFunctions()
|
||||
.withFunctionCallCompiler(callCompiler)
|
||||
.build();
|
||||
// act
|
||||
const act = () => sut.compile(scriptData);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('rethrows error from ScriptCode with script name', () => {
|
||||
// arrange
|
||||
const scriptName = 'scriptName';
|
||||
const expectedError = `Script "${scriptName}" code is empty or undefined`;
|
||||
const callCompiler: IFunctionCallCompiler = {
|
||||
compileCall: () => ({ code: undefined, revertCode: undefined }),
|
||||
};
|
||||
const scriptData = ScriptDataStub.createWithCall()
|
||||
.withName(scriptName);
|
||||
const sut = new ScriptCompilerBuilder()
|
||||
.withSomeFunctions()
|
||||
.withFunctionCallCompiler(callCompiler)
|
||||
.build();
|
||||
// act
|
||||
const act = () => sut.compile(scriptData);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('returns true if "call" is defined', () => {
|
||||
// arrange
|
||||
const sut = new ScriptCompilerBuilder()
|
||||
.withEmptyFunctions()
|
||||
.build();
|
||||
const script = ScriptDataStub.createWithCall();
|
||||
// act
|
||||
const actual = sut.canCompile(script);
|
||||
// assert
|
||||
expect(actual).to.equal(true);
|
||||
});
|
||||
it('returns false if "call" is undefined', () => {
|
||||
// arrange
|
||||
const sut = new ScriptCompilerBuilder()
|
||||
.withEmptyFunctions()
|
||||
.build();
|
||||
const script = ScriptDataStub.createWithCode();
|
||||
// act
|
||||
const actual = sut.canCompile(script);
|
||||
// assert
|
||||
expect(actual).to.equal(false);
|
||||
});
|
||||
});
|
||||
describe('compile', () => {
|
||||
it('throws if script is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined script';
|
||||
const argument = undefined;
|
||||
const builder = new ScriptCompilerBuilder()
|
||||
.withEmptyFunctions()
|
||||
.build();
|
||||
// act
|
||||
const act = () => builder.compile(argument);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('returns code as expected', () => {
|
||||
// arrange
|
||||
const expected: ICompiledCode = {
|
||||
code: 'expected-code',
|
||||
revertCode: 'expected-revert-code',
|
||||
};
|
||||
const call = new FunctionCallDataStub();
|
||||
const script = ScriptDataStub.createWithCall(call);
|
||||
const functions = [FunctionDataStub.createWithCode().withName('existing-func')];
|
||||
const compiledFunctions = new SharedFunctionCollectionStub();
|
||||
const functionParserMock = new SharedFunctionsParserStub();
|
||||
functionParserMock.setup(functions, compiledFunctions);
|
||||
const callCompilerMock = new FunctionCallCompilerStub();
|
||||
callCompilerMock.setup(parseFunctionCalls(call), compiledFunctions, expected);
|
||||
const sut = new ScriptCompilerBuilder()
|
||||
.withFunctions(...functions)
|
||||
.withSharedFunctionsParser(functionParserMock)
|
||||
.withFunctionCallCompiler(callCompilerMock)
|
||||
.build();
|
||||
// act
|
||||
const code = sut.compile(script);
|
||||
// assert
|
||||
expect(code.execute).to.equal(expected.code);
|
||||
expect(code.revert).to.equal(expected.revertCode);
|
||||
});
|
||||
it('creates with expected syntax', () => {
|
||||
// arrange
|
||||
let isUsed = false;
|
||||
const syntax: ILanguageSyntax = {
|
||||
get commentDelimiters() {
|
||||
isUsed = true;
|
||||
return [];
|
||||
},
|
||||
get commonCodeParts() {
|
||||
isUsed = true;
|
||||
return [];
|
||||
},
|
||||
};
|
||||
const sut = new ScriptCompilerBuilder()
|
||||
.withSomeFunctions()
|
||||
.withSyntax(syntax)
|
||||
.build();
|
||||
const scriptData = ScriptDataStub.createWithCall();
|
||||
// act
|
||||
sut.compile(scriptData);
|
||||
// assert
|
||||
expect(isUsed).to.equal(true);
|
||||
});
|
||||
it('rethrows error with script name', () => {
|
||||
// arrange
|
||||
const scriptName = 'scriptName';
|
||||
const innerError = 'innerError';
|
||||
const expectedError = `Script "${scriptName}" ${innerError}`;
|
||||
const callCompiler: IFunctionCallCompiler = {
|
||||
compileCall: () => { throw new Error(innerError); },
|
||||
};
|
||||
const scriptData = ScriptDataStub.createWithCall()
|
||||
.withName(scriptName);
|
||||
const sut = new ScriptCompilerBuilder()
|
||||
.withSomeFunctions()
|
||||
.withFunctionCallCompiler(callCompiler)
|
||||
.build();
|
||||
// act
|
||||
const act = () => sut.compile(scriptData);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('rethrows error from ScriptCode with script name', () => {
|
||||
// arrange
|
||||
const scriptName = 'scriptName';
|
||||
const expectedError = `Script "${scriptName}" code is empty or undefined`;
|
||||
const callCompiler: IFunctionCallCompiler = {
|
||||
compileCall: () => ({ code: undefined, revertCode: undefined }),
|
||||
};
|
||||
const scriptData = ScriptDataStub.createWithCall()
|
||||
.withName(scriptName);
|
||||
const sut = new ScriptCompilerBuilder()
|
||||
.withSomeFunctions()
|
||||
.withFunctionCallCompiler(callCompiler)
|
||||
.build();
|
||||
// act
|
||||
const act = () => sut.compile(scriptData);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class ScriptCompilerBuilder {
|
||||
private static createFunctions(...names: string[]): FunctionData[] {
|
||||
return names.map((functionName) => {
|
||||
return FunctionDataStub.createWithCode().withName(functionName);
|
||||
});
|
||||
}
|
||||
private functions: FunctionData[];
|
||||
private syntax: ILanguageSyntax = new LanguageSyntaxStub();
|
||||
private sharedFunctionsParser: ISharedFunctionsParser = new SharedFunctionsParserStub();
|
||||
private callCompiler: IFunctionCallCompiler = new FunctionCallCompilerStub();
|
||||
public withFunctions(...functions: FunctionData[]): ScriptCompilerBuilder {
|
||||
this.functions = functions;
|
||||
return this;
|
||||
}
|
||||
public withSomeFunctions(): ScriptCompilerBuilder {
|
||||
this.functions = ScriptCompilerBuilder.createFunctions('test-function');
|
||||
return this;
|
||||
}
|
||||
public withFunctionNames(...functionNames: string[]): ScriptCompilerBuilder {
|
||||
this.functions = ScriptCompilerBuilder.createFunctions(...functionNames);
|
||||
return this;
|
||||
}
|
||||
public withEmptyFunctions(): ScriptCompilerBuilder {
|
||||
this.functions = [];
|
||||
return this;
|
||||
}
|
||||
public withSyntax(syntax: ILanguageSyntax): ScriptCompilerBuilder {
|
||||
this.syntax = syntax;
|
||||
return this;
|
||||
}
|
||||
public withSharedFunctionsParser(SharedFunctionsParser: ISharedFunctionsParser): ScriptCompilerBuilder {
|
||||
this.sharedFunctionsParser = SharedFunctionsParser;
|
||||
return this;
|
||||
}
|
||||
public withFunctionCallCompiler(callCompiler: IFunctionCallCompiler): ScriptCompilerBuilder {
|
||||
this.callCompiler = callCompiler;
|
||||
return this;
|
||||
}
|
||||
public build(): ScriptCompiler {
|
||||
if (!this.functions) {
|
||||
throw new Error('Function behavior not defined');
|
||||
}
|
||||
return new ScriptCompiler(this.functions, this.syntax, this.sharedFunctionsParser, this.callCompiler);
|
||||
private static createFunctions(...names: string[]): FunctionData[] {
|
||||
return names.map((functionName) => {
|
||||
return FunctionDataStub.createWithCode().withName(functionName);
|
||||
});
|
||||
}
|
||||
|
||||
private functions: FunctionData[];
|
||||
|
||||
private syntax: ILanguageSyntax = new LanguageSyntaxStub();
|
||||
|
||||
private sharedFunctionsParser: ISharedFunctionsParser = new SharedFunctionsParserStub();
|
||||
|
||||
private callCompiler: IFunctionCallCompiler = new FunctionCallCompilerStub();
|
||||
|
||||
public withFunctions(...functions: FunctionData[]): ScriptCompilerBuilder {
|
||||
this.functions = functions;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withSomeFunctions(): ScriptCompilerBuilder {
|
||||
this.functions = ScriptCompilerBuilder.createFunctions('test-function');
|
||||
return this;
|
||||
}
|
||||
|
||||
public withFunctionNames(...functionNames: string[]): ScriptCompilerBuilder {
|
||||
this.functions = ScriptCompilerBuilder.createFunctions(...functionNames);
|
||||
return this;
|
||||
}
|
||||
|
||||
public withEmptyFunctions(): ScriptCompilerBuilder {
|
||||
this.functions = [];
|
||||
return this;
|
||||
}
|
||||
|
||||
public withSyntax(syntax: ILanguageSyntax): ScriptCompilerBuilder {
|
||||
this.syntax = syntax;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withSharedFunctionsParser(
|
||||
sharedFunctionsParser: ISharedFunctionsParser,
|
||||
): ScriptCompilerBuilder {
|
||||
this.sharedFunctionsParser = sharedFunctionsParser;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withFunctionCallCompiler(callCompiler: IFunctionCallCompiler): ScriptCompilerBuilder {
|
||||
this.callCompiler = callCompiler;
|
||||
return this;
|
||||
}
|
||||
|
||||
public build(): ScriptCompiler {
|
||||
if (!this.functions) {
|
||||
throw new Error('Function behavior not defined');
|
||||
}
|
||||
return new ScriptCompiler(
|
||||
this.functions,
|
||||
this.syntax,
|
||||
this.callCompiler,
|
||||
this.sharedFunctionsParser,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,176 +12,175 @@ import { CategoryCollectionParseContextStub } from '@tests/unit/stubs/CategoryCo
|
||||
import { LanguageSyntaxStub } from '@tests/unit/stubs/LanguageSyntaxStub';
|
||||
|
||||
describe('ScriptParser', () => {
|
||||
describe('parseScript', () => {
|
||||
it('parses name as expected', () => {
|
||||
// arrange
|
||||
const expected = 'test-expected-name';
|
||||
const script = ScriptDataStub.createWithCode()
|
||||
.withName(expected);
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
// act
|
||||
const actual = parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(actual.name).to.equal(expected);
|
||||
});
|
||||
it('parses docs as expected', () => {
|
||||
// arrange
|
||||
const docs = [ 'https://expected-doc1.com', 'https://expected-doc2.com' ];
|
||||
const script = ScriptDataStub.createWithCode()
|
||||
.withDocs(docs);
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
const expected = parseDocUrls(script);
|
||||
// act
|
||||
const actual = parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(actual.documentationUrls).to.deep.equal(expected);
|
||||
});
|
||||
describe('invalid script', () => {
|
||||
it('throws when script is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined script';
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
const script = undefined;
|
||||
// act
|
||||
const act = () => parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws when both function call and code are defined', () => {
|
||||
// arrange
|
||||
const expectedError = 'cannot define both "call" and "code"';
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
const script = ScriptDataStub
|
||||
.createWithCall()
|
||||
.withCode('code');
|
||||
// act
|
||||
const act = () => parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws when both function call and revertCode are defined', () => {
|
||||
// arrange
|
||||
const expectedError = 'cannot define "revertCode" if "call" is defined';
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
const script = ScriptDataStub
|
||||
.createWithCall()
|
||||
.withRevertCode('revert-code');
|
||||
// act
|
||||
const act = () => parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws when neither call or revertCode are defined', () => {
|
||||
// arrange
|
||||
const expectedError = 'must define either "call" or "code"';
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
const script = ScriptDataStub.createWithoutCallOrCodes();
|
||||
// act
|
||||
const act = () => parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('level', () => {
|
||||
it('accepts undefined level', () => {
|
||||
const undefinedLevels: string[] = [ '', undefined ];
|
||||
undefinedLevels.forEach((undefinedLevel) => {
|
||||
// arrange
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
const script = ScriptDataStub.createWithCode()
|
||||
.withRecommend(undefinedLevel);
|
||||
// act
|
||||
const actual = parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(actual.level).to.equal(undefined);
|
||||
});
|
||||
});
|
||||
describe('parses level as expected', () => {
|
||||
// arrange
|
||||
const expectedLevel = RecommendationLevel.Standard;
|
||||
const expectedName = 'level';
|
||||
const levelText = 'standard';
|
||||
const script = ScriptDataStub.createWithCode()
|
||||
.withRecommend(levelText);
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
const parserMock = new EnumParserStub<RecommendationLevel>()
|
||||
.setup(expectedName, levelText, expectedLevel);
|
||||
// act
|
||||
const actual = parseScript(script, parseContext, parserMock);
|
||||
// assert
|
||||
expect(actual.level).to.equal(expectedLevel);
|
||||
});
|
||||
});
|
||||
describe('code', () => {
|
||||
it('parses "execute" as expected', () => {
|
||||
// arrange
|
||||
const expected = 'expected-code';
|
||||
const script = ScriptDataStub
|
||||
.createWithCode()
|
||||
.withCode(expected);
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
// act
|
||||
const parsed = parseScript(script, parseContext);
|
||||
// assert
|
||||
const actual = parsed.code.execute;
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
it('parses "revert" as expected', () => {
|
||||
// arrange
|
||||
const expected = 'expected-revert-code';
|
||||
const script = ScriptDataStub
|
||||
.createWithCode()
|
||||
.withRevertCode(expected);
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
// act
|
||||
const parsed = parseScript(script, parseContext);
|
||||
// assert
|
||||
const actual = parsed.code.revert;
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
describe('compiler', () => {
|
||||
it('throws when context is not defined', () => {
|
||||
// arrange
|
||||
const expectedMessage = 'undefined context';
|
||||
const script = ScriptDataStub.createWithCode();
|
||||
const context: ICategoryCollectionParseContext = undefined;
|
||||
// act
|
||||
const act = () => parseScript(script, context);
|
||||
// assert
|
||||
expect(act).to.throw(expectedMessage);
|
||||
});
|
||||
it('gets code from compiler', () => {
|
||||
// arrange
|
||||
const expected = new ScriptCodeStub();
|
||||
const script = ScriptDataStub.createWithCode();
|
||||
const compiler = new ScriptCompilerStub()
|
||||
.withCompileAbility(script, expected);
|
||||
const parseContext = new CategoryCollectionParseContextStub()
|
||||
.withCompiler(compiler);
|
||||
// act
|
||||
const parsed = parseScript(script, parseContext);
|
||||
// assert
|
||||
const actual = parsed.code;
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('syntax', () => {
|
||||
it('set from the context', () => { // test through script validation logic
|
||||
// arrange
|
||||
const commentDelimiter = 'should not throw';
|
||||
const duplicatedCode = `${commentDelimiter} duplicate-line\n${commentDelimiter} duplicate-line`;
|
||||
const parseContext = new CategoryCollectionParseContextStub()
|
||||
.withSyntax(new LanguageSyntaxStub().withCommentDelimiters(commentDelimiter));
|
||||
const script = ScriptDataStub
|
||||
.createWithoutCallOrCodes()
|
||||
.withCode(duplicatedCode);
|
||||
// act
|
||||
const act = () => parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(act).to.not.throw();
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('parseScript', () => {
|
||||
it('parses name as expected', () => {
|
||||
// arrange
|
||||
const expected = 'test-expected-name';
|
||||
const script = ScriptDataStub.createWithCode()
|
||||
.withName(expected);
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
// act
|
||||
const actual = parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(actual.name).to.equal(expected);
|
||||
});
|
||||
it('parses docs as expected', () => {
|
||||
// arrange
|
||||
const docs = ['https://expected-doc1.com', 'https://expected-doc2.com'];
|
||||
const script = ScriptDataStub.createWithCode()
|
||||
.withDocs(docs);
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
const expected = parseDocUrls(script);
|
||||
// act
|
||||
const actual = parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(actual.documentationUrls).to.deep.equal(expected);
|
||||
});
|
||||
describe('invalid script', () => {
|
||||
it('throws when script is undefined', () => {
|
||||
// arrange
|
||||
const expectedError = 'undefined script';
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
const script = undefined;
|
||||
// act
|
||||
const act = () => parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws when both function call and code are defined', () => {
|
||||
// arrange
|
||||
const expectedError = 'cannot define both "call" and "code"';
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
const script = ScriptDataStub
|
||||
.createWithCall()
|
||||
.withCode('code');
|
||||
// act
|
||||
const act = () => parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws when both function call and revertCode are defined', () => {
|
||||
// arrange
|
||||
const expectedError = 'cannot define "revertCode" if "call" is defined';
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
const script = ScriptDataStub
|
||||
.createWithCall()
|
||||
.withRevertCode('revert-code');
|
||||
// act
|
||||
const act = () => parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws when neither call or revertCode are defined', () => {
|
||||
// arrange
|
||||
const expectedError = 'must define either "call" or "code"';
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
const script = ScriptDataStub.createWithoutCallOrCodes();
|
||||
// act
|
||||
const act = () => parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('level', () => {
|
||||
it('accepts undefined level', () => {
|
||||
const undefinedLevels: string[] = ['', undefined];
|
||||
undefinedLevels.forEach((undefinedLevel) => {
|
||||
// arrange
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
const script = ScriptDataStub.createWithCode()
|
||||
.withRecommend(undefinedLevel);
|
||||
// act
|
||||
const actual = parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(actual.level).to.equal(undefined);
|
||||
});
|
||||
});
|
||||
describe('parses level as expected', () => {
|
||||
// arrange
|
||||
const expectedLevel = RecommendationLevel.Standard;
|
||||
const expectedName = 'level';
|
||||
const levelText = 'standard';
|
||||
const script = ScriptDataStub.createWithCode()
|
||||
.withRecommend(levelText);
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
const parserMock = new EnumParserStub<RecommendationLevel>()
|
||||
.setup(expectedName, levelText, expectedLevel);
|
||||
// act
|
||||
const actual = parseScript(script, parseContext, parserMock);
|
||||
// assert
|
||||
expect(actual.level).to.equal(expectedLevel);
|
||||
});
|
||||
});
|
||||
describe('code', () => {
|
||||
it('parses "execute" as expected', () => {
|
||||
// arrange
|
||||
const expected = 'expected-code';
|
||||
const script = ScriptDataStub
|
||||
.createWithCode()
|
||||
.withCode(expected);
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
// act
|
||||
const parsed = parseScript(script, parseContext);
|
||||
// assert
|
||||
const actual = parsed.code.execute;
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
it('parses "revert" as expected', () => {
|
||||
// arrange
|
||||
const expected = 'expected-revert-code';
|
||||
const script = ScriptDataStub
|
||||
.createWithCode()
|
||||
.withRevertCode(expected);
|
||||
const parseContext = new CategoryCollectionParseContextStub();
|
||||
// act
|
||||
const parsed = parseScript(script, parseContext);
|
||||
// assert
|
||||
const actual = parsed.code.revert;
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
describe('compiler', () => {
|
||||
it('throws when context is not defined', () => {
|
||||
// arrange
|
||||
const expectedMessage = 'undefined context';
|
||||
const script = ScriptDataStub.createWithCode();
|
||||
const context: ICategoryCollectionParseContext = undefined;
|
||||
// act
|
||||
const act = () => parseScript(script, context);
|
||||
// assert
|
||||
expect(act).to.throw(expectedMessage);
|
||||
});
|
||||
it('gets code from compiler', () => {
|
||||
// arrange
|
||||
const expected = new ScriptCodeStub();
|
||||
const script = ScriptDataStub.createWithCode();
|
||||
const compiler = new ScriptCompilerStub()
|
||||
.withCompileAbility(script, expected);
|
||||
const parseContext = new CategoryCollectionParseContextStub()
|
||||
.withCompiler(compiler);
|
||||
// act
|
||||
const parsed = parseScript(script, parseContext);
|
||||
// assert
|
||||
const actual = parsed.code;
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
});
|
||||
describe('syntax', () => {
|
||||
it('set from the context', () => { // test through script validation logic
|
||||
// arrange
|
||||
const commentDelimiter = 'should not throw';
|
||||
const duplicatedCode = `${commentDelimiter} duplicate-line\n${commentDelimiter} duplicate-line`;
|
||||
const parseContext = new CategoryCollectionParseContextStub()
|
||||
.withSyntax(new LanguageSyntaxStub().withCommentDelimiters(commentDelimiter));
|
||||
const script = ScriptDataStub
|
||||
.createWithoutCallOrCodes()
|
||||
.withCode(duplicatedCode);
|
||||
// act
|
||||
const act = () => parseScript(script, parseContext);
|
||||
// assert
|
||||
expect(act).to.not.throw();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -5,28 +5,28 @@ import { BatchFileSyntax } from '@/application/Parser/Script/Syntax/BatchFileSyn
|
||||
import { ShellScriptSyntax } from '@/application/Parser/Script/Syntax/ShellScriptSyntax';
|
||||
|
||||
function getSystemsUnderTest(): ILanguageSyntax[] {
|
||||
return [ new BatchFileSyntax(), new ShellScriptSyntax() ];
|
||||
return [new BatchFileSyntax(), new ShellScriptSyntax()];
|
||||
}
|
||||
|
||||
describe('ConcreteSyntaxes', () => {
|
||||
describe('commentDelimiters', () => {
|
||||
for (const sut of getSystemsUnderTest()) {
|
||||
it(`${sut.constructor.name} returns defined value`, () => {
|
||||
// act
|
||||
const value = sut.commentDelimiters;
|
||||
// assert
|
||||
expect(value);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('commonCodeParts', () => {
|
||||
for (const sut of getSystemsUnderTest()) {
|
||||
it(`${sut.constructor.name} returns defined value`, () => {
|
||||
// act
|
||||
const value = sut.commonCodeParts;
|
||||
// assert
|
||||
expect(value);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('commentDelimiters', () => {
|
||||
for (const sut of getSystemsUnderTest()) {
|
||||
it(`${sut.constructor.name} returns defined value`, () => {
|
||||
// act
|
||||
const value = sut.commentDelimiters;
|
||||
// assert
|
||||
expect(value);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('commonCodeParts', () => {
|
||||
for (const sut of getSystemsUnderTest()) {
|
||||
it(`${sut.constructor.name} returns defined value`, () => {
|
||||
// act
|
||||
const value = sut.commonCodeParts;
|
||||
// assert
|
||||
expect(value);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,9 +6,9 @@ import { BatchFileSyntax } from '@/application/Parser/Script/Syntax/BatchFileSyn
|
||||
import { ScriptingLanguageFactoryTestRunner } from '@tests/unit/application/Common/ScriptingLanguage/ScriptingLanguageFactoryTestRunner';
|
||||
|
||||
describe('SyntaxFactory', () => {
|
||||
const sut = new SyntaxFactory();
|
||||
const runner = new ScriptingLanguageFactoryTestRunner()
|
||||
.expect(ScriptingLanguage.shellscript, ShellScriptSyntax)
|
||||
.expect(ScriptingLanguage.batchfile, BatchFileSyntax);
|
||||
runner.testCreateMethod(sut);
|
||||
const sut = new SyntaxFactory();
|
||||
const runner = new ScriptingLanguageFactoryTestRunner()
|
||||
.expect(ScriptingLanguage.shellscript, ShellScriptSyntax)
|
||||
.expect(ScriptingLanguage.batchfile, BatchFileSyntax);
|
||||
runner.testCreateMethod(sut);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user