Refactor to unify scripts/categories as Executable
This commit consolidates scripts and categories under a unified 'Executable' concept. This simplifies the architecture and improves code readability. - Introduce subfolders within `src/domain` to segregate domain elements. - Update class and interface names by removing the 'I' prefix in alignment with new coding standards. - Replace 'Node' with 'Executable' to clarify usage; reserve 'Node' exclusively for the UI's tree component.
This commit is contained in:
@@ -0,0 +1,240 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { ExpressionPosition } from '@/application/Parser/Executable/Script/Compiler/Expressions/Expression/ExpressionPosition';
|
||||
import { type ExpressionEvaluator, Expression } from '@/application/Parser/Executable/Script/Compiler/Expressions/Expression/Expression';
|
||||
import type { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/IFunctionCallArgumentCollection';
|
||||
import { FunctionCallArgumentCollectionStub } from '@tests/unit/shared/Stubs/FunctionCallArgumentCollectionStub';
|
||||
import { FunctionParameterCollectionStub } from '@tests/unit/shared/Stubs/FunctionParameterCollectionStub';
|
||||
import { FunctionCallArgumentStub } from '@tests/unit/shared/Stubs/FunctionCallArgumentStub';
|
||||
import { ExpressionEvaluationContextStub } from '@tests/unit/shared/Stubs/ExpressionEvaluationContextStub';
|
||||
import type { IPipelineCompiler } from '@/application/Parser/Executable/Script/Compiler/Expressions/Pipes/IPipelineCompiler';
|
||||
import { PipelineCompilerStub } from '@tests/unit/shared/Stubs/PipelineCompilerStub';
|
||||
import type { IReadOnlyFunctionParameterCollection } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/IFunctionParameterCollection';
|
||||
import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
import type { IExpressionEvaluationContext } from '@/application/Parser/Executable/Script/Compiler/Expressions/Expression/ExpressionEvaluationContext';
|
||||
import { expectExists } from '@tests/shared/Assertions/ExpectExists';
|
||||
import { formatAssertionMessage } from '@tests/shared/FormatAssertionMessage';
|
||||
|
||||
describe('Expression', () => {
|
||||
describe('ctor', () => {
|
||||
describe('position', () => {
|
||||
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', () => {
|
||||
describe('defaults to empty array if absent', () => {
|
||||
itEachAbsentObjectValue((absentValue) => {
|
||||
// arrange
|
||||
const parameters = absentValue;
|
||||
// act
|
||||
const actual = new ExpressionBuilder()
|
||||
.withParameters(parameters)
|
||||
.build();
|
||||
// assert
|
||||
expect(actual.parameters);
|
||||
expect(actual.parameters.all);
|
||||
expect(actual.parameters.all.length).to.equal(0);
|
||||
}, { excludeNull: true });
|
||||
});
|
||||
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('evaluate', () => {
|
||||
describe('throws with invalid arguments', () => {
|
||||
const testCases: readonly {
|
||||
name: string,
|
||||
context: IExpressionEvaluationContext,
|
||||
expectedError: string,
|
||||
sutBuilder?: (builder: ExpressionBuilder) => ExpressionBuilder,
|
||||
}[] = [
|
||||
{
|
||||
name: 'throws when some of the required args are not provided',
|
||||
sutBuilder: (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',
|
||||
sutBuilder: (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.sutBuilder) {
|
||||
testCase.sutBuilder(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, formatAssertionMessage([
|
||||
`Given arguments: ${JSON.stringify(givenArguments)}`,
|
||||
`Expected parameter names: ${JSON.stringify(expectedParameterNames)}`,
|
||||
]));
|
||||
});
|
||||
it('sends pipeline compiler as it is', () => {
|
||||
// arrange
|
||||
const expected = new PipelineCompilerStub();
|
||||
const context = new ExpressionEvaluationContextStub()
|
||||
.withPipelineCompiler(expected);
|
||||
let actual: IPipelineCompiler | undefined;
|
||||
const evaluatorMock: ExpressionEvaluator = (c) => {
|
||||
actual = c.pipelineCompiler;
|
||||
return '';
|
||||
};
|
||||
const sut = new ExpressionBuilder()
|
||||
.withEvaluator(evaluatorMock)
|
||||
.build();
|
||||
// arrange
|
||||
sut.evaluate(context);
|
||||
// assert
|
||||
expectExists(actual);
|
||||
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 | undefined;
|
||||
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
|
||||
expectExists(actual);
|
||||
const collection = actual;
|
||||
const actualArguments = collection.getAllParameterNames()
|
||||
.map((name) => collection.getArgument(name));
|
||||
expect(actualArguments).to.deep.equal(testCase.expectedArguments);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class ExpressionBuilder {
|
||||
private position: ExpressionPosition = new ExpressionPosition(0, 5);
|
||||
|
||||
private parameters?: IReadOnlyFunctionParameterCollection = new FunctionParameterCollectionStub();
|
||||
|
||||
public withPosition(position: ExpressionPosition) {
|
||||
this.position = position;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withEvaluator(evaluator: ExpressionEvaluator) {
|
||||
this.evaluator = evaluator;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withParameters(parameters: IReadOnlyFunctionParameterCollection | undefined) {
|
||||
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({
|
||||
position: this.position,
|
||||
evaluator: this.evaluator,
|
||||
parameters: this.parameters,
|
||||
});
|
||||
}
|
||||
|
||||
private evaluator: ExpressionEvaluator = () => `[${ExpressionBuilder.name}] evaluated-expression`;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { ExpressionEvaluationContext, type IExpressionEvaluationContext } from '@/application/Parser/Executable/Script/Compiler/Expressions/Expression/ExpressionEvaluationContext';
|
||||
import type { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/IFunctionCallArgumentCollection';
|
||||
import type { IPipelineCompiler } from '@/application/Parser/Executable/Script/Compiler/Expressions/Pipes/IPipelineCompiler';
|
||||
import { FunctionCallArgumentCollectionStub } from '@tests/unit/shared/Stubs/FunctionCallArgumentCollectionStub';
|
||||
import { PipelineCompilerStub } from '@tests/unit/shared/Stubs/PipelineCompilerStub';
|
||||
|
||||
describe('ExpressionEvaluationContext', () => {
|
||||
describe('ctor', () => {
|
||||
describe('args', () => {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { ExpressionPosition } from '@/application/Parser/Executable/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('isInInsideOf', () => {
|
||||
// arrange
|
||||
const testCases: readonly {
|
||||
name: string,
|
||||
sut: ExpressionPosition,
|
||||
potentialParent: ExpressionPosition,
|
||||
expectedResult: boolean,
|
||||
}[] = [
|
||||
{
|
||||
name: 'true; when other contains sut inside boundaries',
|
||||
sut: new ExpressionPosition(4, 8),
|
||||
potentialParent: new ExpressionPosition(0, 10),
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: 'true; when other contains sut with same upper boundary',
|
||||
sut: new ExpressionPosition(4, 10),
|
||||
potentialParent: new ExpressionPosition(0, 10),
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: 'true; when other contains sut with same lower boundary',
|
||||
sut: new ExpressionPosition(0, 8),
|
||||
potentialParent: new ExpressionPosition(0, 10),
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: 'false; when other is same as sut',
|
||||
sut: new ExpressionPosition(0, 10),
|
||||
potentialParent: new ExpressionPosition(0, 10),
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: 'false; when sut contains other',
|
||||
sut: new ExpressionPosition(0, 10),
|
||||
potentialParent: new ExpressionPosition(4, 8),
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: 'false; when sut starts and ends before other',
|
||||
sut: new ExpressionPosition(0, 10),
|
||||
potentialParent: new ExpressionPosition(15, 25),
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: 'false; when sut starts before other but ends inside other',
|
||||
sut: new ExpressionPosition(0, 10),
|
||||
potentialParent: new ExpressionPosition(5, 10),
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: 'false; when sut starts inside other but ends after other',
|
||||
sut: new ExpressionPosition(5, 11),
|
||||
potentialParent: new ExpressionPosition(0, 10),
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: 'false; when sut starts at same position but end after other',
|
||||
sut: new ExpressionPosition(0, 11),
|
||||
potentialParent: new ExpressionPosition(0, 10),
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: 'false; when sut ends at same positions but start before other',
|
||||
sut: new ExpressionPosition(0, 10),
|
||||
potentialParent: new ExpressionPosition(1, 10),
|
||||
expectedResult: false,
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// act
|
||||
const actual = testCase.sut.isInInsideOf(testCase.potentialParent);
|
||||
// assert
|
||||
expect(actual).to.equal(testCase.expectedResult);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('isSame', () => {
|
||||
// arrange
|
||||
const testCases: readonly {
|
||||
name: string,
|
||||
sut: ExpressionPosition,
|
||||
other: ExpressionPosition,
|
||||
expectedResult: boolean,
|
||||
}[] = [
|
||||
{
|
||||
name: 'true; when positions are same',
|
||||
sut: new ExpressionPosition(0, 10),
|
||||
other: new ExpressionPosition(0, 10),
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: 'false; when start position is different',
|
||||
sut: new ExpressionPosition(0, 10),
|
||||
other: new ExpressionPosition(1, 10),
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: 'false; when end position is different',
|
||||
sut: new ExpressionPosition(0, 10),
|
||||
other: new ExpressionPosition(0, 11),
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: 'false; when both start and end positions are different',
|
||||
sut: new ExpressionPosition(0, 10),
|
||||
other: new ExpressionPosition(20, 30),
|
||||
expectedResult: false,
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// act
|
||||
const actual = testCase.sut.isSame(testCase.other);
|
||||
// assert
|
||||
expect(actual).to.equal(testCase.expectedResult);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('isIntersecting', () => {
|
||||
// arrange
|
||||
const testCases: readonly {
|
||||
name: string,
|
||||
first: ExpressionPosition,
|
||||
second: ExpressionPosition,
|
||||
expectedResult: boolean,
|
||||
}[] = [
|
||||
{
|
||||
name: 'true; when one contains other inside boundaries',
|
||||
first: new ExpressionPosition(4, 8),
|
||||
second: new ExpressionPosition(0, 10),
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: 'true; when one starts inside other\'s ending boundary without being contained',
|
||||
first: new ExpressionPosition(0, 10),
|
||||
second: new ExpressionPosition(9, 15),
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: 'true; when positions are the same',
|
||||
first: new ExpressionPosition(0, 5),
|
||||
second: new ExpressionPosition(0, 5),
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: 'true; when one starts inside other\'s starting boundary without being contained',
|
||||
first: new ExpressionPosition(5, 10),
|
||||
second: new ExpressionPosition(5, 11),
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: 'false; when one starts directly after other',
|
||||
first: new ExpressionPosition(0, 10),
|
||||
second: new ExpressionPosition(10, 20),
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: 'false; when one starts after other with margin',
|
||||
first: new ExpressionPosition(0, 10),
|
||||
second: new ExpressionPosition(100, 200),
|
||||
expectedResult: false,
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(testCase.name, () => {
|
||||
// act
|
||||
const actual = testCase.first.isIntersecting(testCase.second);
|
||||
// assert
|
||||
expect(actual).to.equal(testCase.expectedResult);
|
||||
});
|
||||
it(`reversed: ${testCase.name}`, () => {
|
||||
// act
|
||||
const actual = testCase.second.isIntersecting(testCase.first);
|
||||
// assert
|
||||
expect(actual).to.equal(testCase.expectedResult);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,99 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { createPositionFromRegexFullMatch } from '@/application/Parser/Executable/Script/Compiler/Expressions/Expression/ExpressionPositionFactory';
|
||||
import { ExpressionPosition } from '@/application/Parser/Executable/Script/Compiler/Expressions/Expression/ExpressionPosition';
|
||||
import { itIsTransientFactory } from '@tests/unit/shared/TestCases/TransientFactoryTests';
|
||||
|
||||
describe('ExpressionPositionFactory', () => {
|
||||
describe('createPositionFromRegexFullMatch', () => {
|
||||
describe('it is a transient factory', () => {
|
||||
// arrange
|
||||
const fakeMatch = createRegexMatch();
|
||||
// act
|
||||
const create = () => createPositionFromRegexFullMatch(fakeMatch);
|
||||
// assert
|
||||
itIsTransientFactory({
|
||||
getter: create,
|
||||
expectedType: ExpressionPosition,
|
||||
});
|
||||
});
|
||||
it('creates a position with the correct start position', () => {
|
||||
// arrange
|
||||
const expectedStartPosition = 5;
|
||||
const fakeMatch = createRegexMatch({
|
||||
fullMatch: 'matched string',
|
||||
matchIndex: expectedStartPosition,
|
||||
});
|
||||
// act
|
||||
const position = createPositionFromRegexFullMatch(fakeMatch);
|
||||
// assert
|
||||
expect(position.start).toBe(expectedStartPosition);
|
||||
});
|
||||
|
||||
it('creates a position with the correct end position', () => {
|
||||
// arrange
|
||||
const startPosition = 3;
|
||||
const matchedString = 'matched string';
|
||||
const expectedEndPosition = startPosition + matchedString.length;
|
||||
const fakeMatch = createRegexMatch({
|
||||
fullMatch: matchedString,
|
||||
matchIndex: startPosition,
|
||||
});
|
||||
// act
|
||||
const position = createPositionFromRegexFullMatch(fakeMatch);
|
||||
// assert
|
||||
expect(position.end).to.equal(expectedEndPosition);
|
||||
});
|
||||
|
||||
it('creates correct position with capturing groups', () => {
|
||||
// arrange
|
||||
const startPosition = 20;
|
||||
const fakeMatch = createRegexMatch({
|
||||
fullMatch: 'matched string',
|
||||
capturingGroups: ['group1', 'group2'],
|
||||
matchIndex: startPosition,
|
||||
});
|
||||
// act
|
||||
const position = createPositionFromRegexFullMatch(fakeMatch);
|
||||
// assert
|
||||
expect(position.start).toBe(startPosition);
|
||||
expect(position.end).toBe(startPosition + fakeMatch[0].length);
|
||||
});
|
||||
|
||||
describe('invalid values', () => {
|
||||
it('throws an error if match.index is undefined', () => {
|
||||
// arrange
|
||||
const fakeMatch = createRegexMatch();
|
||||
fakeMatch.index = undefined;
|
||||
const expectedError = `Regex match did not yield any results: ${JSON.stringify(fakeMatch)}`;
|
||||
// act
|
||||
const act = () => createPositionFromRegexFullMatch(fakeMatch);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
it('throws an error for empty match', () => {
|
||||
// arrange
|
||||
const fakeMatch = createRegexMatch({
|
||||
fullMatch: '',
|
||||
matchIndex: 0,
|
||||
});
|
||||
const expectedError = `Regex match is empty: ${JSON.stringify(fakeMatch)}`;
|
||||
// act
|
||||
const act = () => createPositionFromRegexFullMatch(fakeMatch);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function createRegexMatch(options?: {
|
||||
readonly fullMatch?: string,
|
||||
readonly capturingGroups?: readonly string[],
|
||||
readonly matchIndex?: number,
|
||||
}): RegExpMatchArray {
|
||||
const fullMatch = options?.fullMatch ?? 'default fake match';
|
||||
const capturingGroups = options?.capturingGroups ?? [];
|
||||
const fakeMatch: RegExpMatchArray = [fullMatch, ...capturingGroups];
|
||||
fakeMatch.index = options?.matchIndex ?? 0;
|
||||
return fakeMatch;
|
||||
}
|
||||
Reference in New Issue
Block a user