This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { ExpressionPosition } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionPosition';
|
||||
import { ExpressionEvaluator } from '@/application/Parser/Script/Compiler/Expressions/Expression/Expression';
|
||||
import { Expression } from '@/application/Parser/Script/Compiler/Expressions/Expression/Expression';
|
||||
import { ExpressionArguments } from '@/application/Parser/Script/Compiler/Expressions/Expression/IExpression';
|
||||
|
||||
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).to.have.lengthOf(0);
|
||||
});
|
||||
it('sets as expected', () => {
|
||||
// arrange
|
||||
const expected = [ 'firstParameterName', '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', () => {
|
||||
it('returns result from evaluator', () => {
|
||||
// arrange
|
||||
const evaluatorMock: ExpressionEvaluator = (args) => JSON.stringify(args);
|
||||
const givenArguments = { parameter1: 'value1', parameter2: 'value2' };
|
||||
const expected = evaluatorMock(givenArguments);
|
||||
const sut = new ExpressionBuilder()
|
||||
.withEvaluator(evaluatorMock)
|
||||
.withParameters(Object.keys(givenArguments))
|
||||
.build();
|
||||
// arrange
|
||||
const actual = sut.evaluate(givenArguments);
|
||||
// assert
|
||||
expect(expected).to.equal(actual);
|
||||
});
|
||||
it('filters unused arguments', () => {
|
||||
// arrange
|
||||
let actual: ExpressionArguments = {};
|
||||
const evaluatorMock: ExpressionEvaluator = (providedArgs) => {
|
||||
Object.keys(providedArgs)
|
||||
.forEach((name) => actual = {...actual, [name]: providedArgs[name] });
|
||||
return '';
|
||||
};
|
||||
const parameterNameToHave = 'parameterToHave';
|
||||
const parameterNameToIgnore = 'parameterToIgnore';
|
||||
const sut = new ExpressionBuilder()
|
||||
.withEvaluator(evaluatorMock)
|
||||
.withParameters([ parameterNameToHave ])
|
||||
.build();
|
||||
const args: ExpressionArguments = {
|
||||
[parameterNameToHave]: 'value-to-have',
|
||||
[parameterNameToIgnore]: 'value-to-ignore',
|
||||
};
|
||||
const expected: ExpressionArguments = {
|
||||
[parameterNameToHave]: args[parameterNameToHave],
|
||||
};
|
||||
// arrange
|
||||
sut.evaluate(args);
|
||||
// assert
|
||||
expect(expected).to.deep.equal(actual);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class ExpressionBuilder {
|
||||
private position: ExpressionPosition = new ExpressionPosition(0, 5);
|
||||
private parameters: readonly string[] = new Array<string>();
|
||||
|
||||
public withPosition(position: ExpressionPosition) {
|
||||
this.position = position;
|
||||
return this;
|
||||
}
|
||||
public withEvaluator(evaluator: ExpressionEvaluator) {
|
||||
this.evaluator = evaluator;
|
||||
return this;
|
||||
}
|
||||
public withParameters(parameters: string[]) {
|
||||
this.parameters = parameters;
|
||||
return this;
|
||||
}
|
||||
public build() {
|
||||
return new Expression(this.position, this.evaluator, this.parameters);
|
||||
}
|
||||
|
||||
private evaluator: ExpressionEvaluator = () => '';
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import 'mocha';
|
||||
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);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,79 +1,127 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { ExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/ExpressionsCompiler';
|
||||
import { IExpressionParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/IExpressionParser';
|
||||
import { ExpressionStub } from '../../../../../stubs/ExpressionStub';
|
||||
import { ExpressionParserStub } from '../../../../../stubs/ExpressionParserStub';
|
||||
|
||||
describe('ExpressionsCompiler', () => {
|
||||
describe('parameter substitution', () => {
|
||||
describe('substitutes as expected', () => {
|
||||
describe('compileExpressions', () => {
|
||||
describe('combines expressions as expected', () => {
|
||||
// arrange
|
||||
const testCases = [ {
|
||||
name: 'with different parameters',
|
||||
code: 'He{{ $firstParameter }} {{ $secondParameter }}!',
|
||||
parameters: {
|
||||
firstParameter: 'llo',
|
||||
secondParameter: 'world',
|
||||
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',
|
||||
},
|
||||
expected: 'Hello world!',
|
||||
}, {
|
||||
name: 'with single parameter',
|
||||
code: '{{ $parameter }}!',
|
||||
parameters: {
|
||||
parameter: 'Hodor',
|
||||
{
|
||||
name: 'unordered expressions',
|
||||
expressions: [
|
||||
new ExpressionStub().withPosition(6, 13).withEvaluatedResult('a'),
|
||||
new ExpressionStub().withPosition(20, 27).withEvaluatedResult('b'),
|
||||
],
|
||||
expected: 'part1 a part2 b part3',
|
||||
},
|
||||
expected: 'Hodor!',
|
||||
|
||||
}];
|
||||
{
|
||||
name: 'with no expressions',
|
||||
expressions: [],
|
||||
expected: code,
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const sut = new MockableExpressionsCompiler();
|
||||
const expressionParserMock = new ExpressionParserStub()
|
||||
.withResult(testCase.expressions);
|
||||
const sut = new MockableExpressionsCompiler(expressionParserMock);
|
||||
// act
|
||||
const actual = sut.compileExpressions(testCase.code, testCase.parameters);
|
||||
const actual = sut.compileExpressions(code);
|
||||
// assert
|
||||
expect(actual).to.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('throws when expected value is not provided', () => {
|
||||
it('passes arguments to expressions as expected', () => {
|
||||
// arrange
|
||||
const expected = {
|
||||
parameter1: 'value1',
|
||||
parameter2: 'value2',
|
||||
};
|
||||
const code = 'non-important';
|
||||
const expressions = [
|
||||
new ExpressionStub(),
|
||||
new ExpressionStub(),
|
||||
];
|
||||
const expressionParserMock = new ExpressionParserStub()
|
||||
.withResult(expressions);
|
||||
const sut = new MockableExpressionsCompiler(expressionParserMock);
|
||||
// act
|
||||
sut.compileExpressions(code, expected);
|
||||
// assert
|
||||
expect(expressions[0].callHistory).to.have.lengthOf(1);
|
||||
expect(expressions[0].callHistory[0]).to.equal(expected);
|
||||
expect(expressions[1].callHistory).to.have.lengthOf(1);
|
||||
expect(expressions[1].callHistory[0]).to.equal(expected);
|
||||
});
|
||||
describe('throws when expected argument is not provided', () => {
|
||||
// arrange
|
||||
const noParameterTestCases = [
|
||||
{
|
||||
name: 'empty parameters',
|
||||
code: '{{ $parameter }}!',
|
||||
parameters: {},
|
||||
expressions: [
|
||||
new ExpressionStub().withParameters('parameter'),
|
||||
],
|
||||
args: {},
|
||||
expectedError: 'parameter value(s) not provided for: "parameter"',
|
||||
},
|
||||
{
|
||||
name: 'undefined parameters',
|
||||
code: '{{ $parameter }}!',
|
||||
parameters: undefined,
|
||||
expressions: [
|
||||
new ExpressionStub().withParameters('parameter'),
|
||||
],
|
||||
args: undefined,
|
||||
expectedError: 'parameter value(s) not provided for: "parameter"',
|
||||
},
|
||||
{
|
||||
name: 'unnecessary parameter provided',
|
||||
code: '{{ $parameter }}!',
|
||||
parameters: {
|
||||
expressions: [
|
||||
new ExpressionStub().withParameters('parameter'),
|
||||
],
|
||||
args: {
|
||||
unnecessaryParameter: 'unnecessaryValue',
|
||||
},
|
||||
expectedError: 'parameter value(s) not provided for: "parameter"',
|
||||
},
|
||||
{
|
||||
name: 'undefined value',
|
||||
code: '{{ $parameter }}!',
|
||||
parameters: {
|
||||
expressions: [
|
||||
new ExpressionStub().withParameters('parameter'),
|
||||
],
|
||||
args: {
|
||||
parameter: undefined,
|
||||
},
|
||||
expectedError: 'parameter value(s) not provided for: "parameter"',
|
||||
},
|
||||
{
|
||||
name: 'multiple values are not',
|
||||
code: '{{ $parameter1 }}, {{ $parameter2 }}, {{ $parameter3 }}',
|
||||
parameters: {},
|
||||
name: 'multiple values are not provided',
|
||||
expressions: [
|
||||
new ExpressionStub().withParameters('parameter1'),
|
||||
new ExpressionStub().withParameters('parameter2', 'parameter3'),
|
||||
],
|
||||
args: {},
|
||||
expectedError: 'parameter value(s) not provided for: "parameter1", "parameter2", "parameter3"',
|
||||
},
|
||||
{
|
||||
name: 'some values are provided',
|
||||
code: '{{ $parameter1 }}, {{ $parameter2 }}, {{ $parameter3 }}',
|
||||
parameters: {
|
||||
expressions: [
|
||||
new ExpressionStub().withParameters('parameter1'),
|
||||
new ExpressionStub().withParameters('parameter2', 'parameter3'),
|
||||
],
|
||||
args: {
|
||||
parameter2: 'value',
|
||||
},
|
||||
expectedError: 'parameter value(s) not provided for: "parameter1", "parameter3"',
|
||||
@@ -81,19 +129,33 @@ describe('ExpressionsCompiler', () => {
|
||||
];
|
||||
for (const testCase of noParameterTestCases) {
|
||||
it(testCase.name, () => {
|
||||
const sut = new MockableExpressionsCompiler();
|
||||
const code = 'non-important-code';
|
||||
const expressionParserMock = new ExpressionParserStub()
|
||||
.withResult(testCase.expressions);
|
||||
const sut = new MockableExpressionsCompiler(expressionParserMock);
|
||||
// act
|
||||
const act = () => sut.compileExpressions(testCase.code, testCase.parameters);
|
||||
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 MockableExpressionsCompiler(expressionParserMock);
|
||||
// act
|
||||
sut.compileExpressions(expected);
|
||||
// assert
|
||||
expect(expressionParserMock.callHistory).to.have.lengthOf(1);
|
||||
expect(expressionParserMock.callHistory[0]).to.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class MockableExpressionsCompiler extends ExpressionsCompiler {
|
||||
constructor() {
|
||||
super();
|
||||
constructor(extractor: IExpressionParser) {
|
||||
super(extractor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { generateIlCode } from '@/application/Parser/Script/Compiler/Expressions/ILCode';
|
||||
|
||||
describe('ILCode', () => {
|
||||
describe('getUniqueParameterNames', () => {
|
||||
// arrange
|
||||
const testCases = [
|
||||
{
|
||||
name: 'empty parameters: returns an empty array',
|
||||
code: 'no expressions',
|
||||
expected: [ ],
|
||||
},
|
||||
{
|
||||
name: 'single parameter: returns expected for single usage',
|
||||
code: '{{ $single }}',
|
||||
expected: [ 'single' ],
|
||||
},
|
||||
{
|
||||
name: 'single parameter: returns distinct values for repeating parameters',
|
||||
code: '{{ $singleRepeating }}, {{ $singleRepeating }}',
|
||||
expected: [ 'singleRepeating' ],
|
||||
},
|
||||
{
|
||||
name: 'multiple parameters: returns expected for single usage of each',
|
||||
code: '{{ $firstParameter }}, {{ $secondParameter }}',
|
||||
expected: [ 'firstParameter', 'secondParameter' ],
|
||||
},
|
||||
{
|
||||
name: 'multiple parameters: returns distinct values for repeating parameters',
|
||||
code: '{{ $firstParameter }}, {{ $firstParameter }}, {{ $firstParameter }} {{ $secondParameter }}, {{ $secondParameter }}',
|
||||
expected: [ 'firstParameter', 'secondParameter' ],
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// act
|
||||
const sut = generateIlCode(testCase.code);
|
||||
const actual = sut.getUniqueParameterNames();
|
||||
// assert
|
||||
expect(actual).to.deep.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('substituteParameter', () => {
|
||||
describe('substitutes by ignoring white spaces inside mustaches', () => {
|
||||
// arrange
|
||||
const mustacheVariations = [
|
||||
'Hello {{ $test }}!',
|
||||
'Hello {{$test }}!',
|
||||
'Hello {{ $test}}!',
|
||||
'Hello {{$test}}!'];
|
||||
mustacheVariations.forEach((variation) => {
|
||||
it(variation, () => {
|
||||
// arrange
|
||||
const ilCode = generateIlCode(variation);
|
||||
const expected = 'Hello world!';
|
||||
// act
|
||||
const actual = ilCode
|
||||
.substituteParameter('test', 'world')
|
||||
.compile();
|
||||
// assert
|
||||
expect(actual).to.deep.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('substitutes as expected', () => {
|
||||
// arrange
|
||||
const testCases = [
|
||||
{
|
||||
name: 'single parameter',
|
||||
code: 'Hello {{ $firstParameter }}!',
|
||||
expected: 'Hello world!',
|
||||
parameters: {
|
||||
firstParameter: 'world',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'single parameter repeated',
|
||||
code: '{{ $firstParameter }} {{ $firstParameter }}!',
|
||||
expected: 'hello hello!',
|
||||
parameters: {
|
||||
firstParameter: 'hello',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'multiple parameters',
|
||||
code: 'He{{ $firstParameter }} {{ $secondParameter }}!',
|
||||
expected: 'Hello world!',
|
||||
parameters: {
|
||||
firstParameter: 'llo',
|
||||
secondParameter: 'world',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'multiple parameters repeated',
|
||||
code: 'He{{ $firstParameter }} {{ $secondParameter }} and He{{ $firstParameter }} {{ $secondParameter }}!',
|
||||
expected: 'Hello world and Hello world!',
|
||||
parameters: {
|
||||
firstParameter: 'llo',
|
||||
secondParameter: 'world',
|
||||
},
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// act
|
||||
let ilCode = generateIlCode(testCase.code);
|
||||
for (const parameterName of Object.keys(testCase.parameters)) {
|
||||
const value = testCase.parameters[parameterName];
|
||||
ilCode = ilCode.substituteParameter(parameterName, value);
|
||||
}
|
||||
const actual = ilCode.compile();
|
||||
// assert
|
||||
expect(actual).to.deep.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('compile', () => {
|
||||
it('throws if there are expressions left', () => {
|
||||
// arrange
|
||||
const expectedError = 'unknown expression: "each"';
|
||||
const code = '{{ each }}';
|
||||
// act
|
||||
const ilCode = generateIlCode(code);
|
||||
const act = () => ilCode.compile();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('returns code as it is if there are no expressions', () => {
|
||||
// arrange
|
||||
const expected = 'I should be the same!';
|
||||
const ilCode = generateIlCode(expected);
|
||||
// act
|
||||
const actual = ilCode.compile();
|
||||
// assert
|
||||
expect(actual).to.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,87 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { IExpression } from '@/application/Parser/Script/Compiler/Expressions/Expression/IExpression';
|
||||
import { IExpressionParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/IExpressionParser';
|
||||
import { CompositeExpressionParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/CompositeExpressionParser';
|
||||
import { ExpressionStub } from '../../../../../../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('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,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { ExpressionEvaluator } from '@/application/Parser/Script/Compiler/Expressions/Expression/Expression';
|
||||
import { IPrimitiveExpression, RegexParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/RegexParser';
|
||||
import { ExpressionPosition } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionPosition';
|
||||
|
||||
describe('RegexParser', () => {
|
||||
describe('findExpressions', () => {
|
||||
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 = [ 'parameter1', 'parameter2' ];
|
||||
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).to.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(),
|
||||
});
|
||||
}
|
||||
function getEvaluatorStub(): ExpressionEvaluator {
|
||||
return () => undefined;
|
||||
}
|
||||
|
||||
function mockPrimitiveExpression(): IPrimitiveExpression {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { ParameterSubstitutionParser } from '@/application/Parser/Script/Compiler/Expressions/SyntaxParsers/ParameterSubstitutionParser';
|
||||
import { ExpressionPosition } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionPosition';
|
||||
import { ExpressionArguments } from '@/application/Parser/Script/Compiler/Expressions/Expression/IExpression';
|
||||
|
||||
describe('ParameterSubstitutionParser', () => {
|
||||
it('finds at expected positions', () => {
|
||||
// arrange
|
||||
const testCases = [ {
|
||||
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) ],
|
||||
}];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const sut = new ParameterSubstitutionParser();
|
||||
// act
|
||||
const expressions = sut.findExpressions(testCase.code);
|
||||
// assert
|
||||
const actual = expressions.map((e) => e.position);
|
||||
expect(actual).to.deep.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
it('evaluates as expected', () => {
|
||||
const testCases = [ {
|
||||
name: 'single parameter',
|
||||
code: '{{ $parameter }}',
|
||||
args: [ {
|
||||
name: 'parameter',
|
||||
value: 'Hello world',
|
||||
}],
|
||||
expected: [ 'Hello world' ],
|
||||
},
|
||||
{
|
||||
name: 'different parameters',
|
||||
code: '{{ $firstParameter }} {{ $secondParameter }}!',
|
||||
args: [ {
|
||||
name: 'firstParameter',
|
||||
value: 'Hello',
|
||||
},
|
||||
{
|
||||
name: 'firstParameter',
|
||||
value: 'World',
|
||||
}],
|
||||
expected: [ 'Hello', 'World' ],
|
||||
}];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
const sut = new ParameterSubstitutionParser();
|
||||
let args: ExpressionArguments = {};
|
||||
for (const arg of testCase.args) {
|
||||
args = {...args, [arg.name]: arg.value };
|
||||
}
|
||||
// act
|
||||
const expressions = sut.findExpressions(testCase.code);
|
||||
// assert
|
||||
const actual = expressions.map((e) => e.evaluate(args));
|
||||
expect(actual).to.deep.equal(testCase.expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user