The goal is to be able to modify values of variables used in templates. It enables future functionality such as escaping, inlining etc. It adds support applying predefined pipes to variables. Pipes can be applied to variable substitution in with and parameter substitution expressions. They work in similar way to piping in Unix where each pipe applied to the compiled result of pipe before. It adds support for using pipes in `with` and parameter substitution expressions. It also refactors how their regex is build to reuse more of the logic by abstracting regex building into a new class. Finally, it separates and extends documentation for templating.
166 lines
8.0 KiB
TypeScript
166 lines
8.0 KiB
TypeScript
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 '@tests/unit/stubs/ExpressionStub';
|
|
import { ExpressionParserStub } from '@tests/unit/stubs/ExpressionParserStub';
|
|
import { FunctionCallArgumentCollectionStub } from '@tests/unit/stubs/FunctionCallArgumentCollectionStub';
|
|
|
|
describe('ExpressionsCompiler', () => {
|
|
describe('compileExpressions', () => {
|
|
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 MockableExpressionsCompiler(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 MockableExpressionsCompiler(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 MockableExpressionsCompiler(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 MockableExpressionsCompiler(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 MockableExpressionsCompiler(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 MockableExpressionsCompiler extends ExpressionsCompiler {
|
|
constructor(extractor: IExpressionParser) {
|
|
super(extractor);
|
|
}
|
|
}
|