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.
123 lines
5.1 KiB
TypeScript
123 lines
5.1 KiB
TypeScript
import 'mocha';
|
|
import { expect } from 'chai';
|
|
import { ExpressionPosition } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionPosition';
|
|
import { IExpressionParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/IExpressionParser';
|
|
import { FunctionCallArgumentCollectionStub } from '@tests/unit/stubs/FunctionCallArgumentCollectionStub';
|
|
import { ExpressionEvaluationContextStub } from '@tests/unit/stubs/ExpressionEvaluationContextStub';
|
|
import { PipelineCompilerStub } from '@tests/unit/stubs/PipelineCompilerStub';
|
|
|
|
export class SyntaxParserTestsRunner {
|
|
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);
|
|
});
|
|
}
|
|
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);
|
|
});
|
|
}
|
|
return this;
|
|
}
|
|
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);
|
|
});
|
|
}
|
|
}
|
|
interface IExpectResultTestCase {
|
|
name: string;
|
|
code: string;
|
|
args: (builder: FunctionCallArgumentCollectionStub) => FunctionCallArgumentCollectionStub;
|
|
expected: readonly string[];
|
|
}
|
|
|
|
interface IExpectPositionTestCase {
|
|
name: string;
|
|
code: string;
|
|
expected: readonly ExpressionPosition[];
|
|
}
|
|
|
|
interface IExpectPipeHitTestData {
|
|
codeBuilder: (pipeline: string) => string;
|
|
parameterName: string;
|
|
parameterValue: string;
|
|
}
|
|
|
|
const PipeTestCases = {
|
|
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',
|
|
|
|
// Wrong cases, but should match anyway and let pipelineCompiler throw errors
|
|
'| pip€', '| pip{e} ',
|
|
],
|
|
InvalidValues: [
|
|
' pipe1 |pipe2', ' pipe1',
|
|
],
|
|
};
|