Add type validation for parameters and fix types
This commit introduces type validation for parameter values within the parser/compiler, aligning with the YAML schema. It aims to eliminate dependencies on side effects in the collection files. This update changes the treatment of data types in the Windows collection, moving away from unintended type casting by the compiler. Previously, numeric and boolean values were used even though only string types were supported. This behavior was unstable and untested, and has now been adjusted to use strings exclusively. Changes ensure that parameter values are correctly validated as strings, enhancing stability and maintainability.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { createTypeValidator } from '@/application/Parser/Common/TypeValidator';
|
||||
import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
import { createTypeValidator, type NonEmptyStringAssertion, type RegexValidationRule } from '@/application/Parser/Common/TypeValidator';
|
||||
import { itEachAbsentObjectValue, itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
|
||||
describe('createTypeValidator', () => {
|
||||
describe('assertObject', () => {
|
||||
@@ -146,6 +146,108 @@ describe('createTypeValidator', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('assertNonEmptyString', () => {
|
||||
describe('with valid string', () => {
|
||||
it('accepts non-empty string without regex rule', () => {
|
||||
// arrange
|
||||
const nonEmptyString = 'hello';
|
||||
const { assertNonEmptyString } = createTypeValidator();
|
||||
// act
|
||||
const act = () => assertNonEmptyString({ value: nonEmptyString, valueName: 'unimportant name' });
|
||||
// assert
|
||||
expect(act).to.not.throw();
|
||||
});
|
||||
it('accepts if the string matches the regex', () => {
|
||||
// arrange
|
||||
const regex: RegExp = /goodbye/;
|
||||
const stringMatchingRegex = 'Valid string containing "goodbye"';
|
||||
const rule: RegexValidationRule = {
|
||||
expectedMatch: regex,
|
||||
errorMessage: 'String contain "goodbye"',
|
||||
};
|
||||
const { assertNonEmptyString } = createTypeValidator();
|
||||
// act
|
||||
const act = () => assertNonEmptyString({
|
||||
value: stringMatchingRegex,
|
||||
valueName: 'unimportant name',
|
||||
rule,
|
||||
});
|
||||
// assert
|
||||
expect(act).to.not.throw();
|
||||
});
|
||||
});
|
||||
describe('with invalid string', () => {
|
||||
describe('throws error for missing string', () => {
|
||||
itEachAbsentStringValue((absentValue) => {
|
||||
// arrange
|
||||
const valueName = 'absent string value';
|
||||
const expectedMessage = `'${valueName}' is missing.`;
|
||||
const { assertNonEmptyString } = createTypeValidator();
|
||||
// act
|
||||
const act = () => assertNonEmptyString({ value: absentValue, valueName });
|
||||
// assert
|
||||
expect(act).to.throw(expectedMessage);
|
||||
});
|
||||
});
|
||||
describe('throws error for non string values', () => {
|
||||
// arrange
|
||||
const testScenarios: readonly {
|
||||
readonly description: string;
|
||||
readonly invalidValue: unknown;
|
||||
}[] = [
|
||||
{
|
||||
description: 'number',
|
||||
invalidValue: 42,
|
||||
},
|
||||
{
|
||||
description: 'boolean',
|
||||
invalidValue: true,
|
||||
},
|
||||
{
|
||||
description: 'object',
|
||||
invalidValue: { property: 'value' },
|
||||
},
|
||||
{
|
||||
description: 'array',
|
||||
invalidValue: ['a', 'r', 'r', 'a', 'y'],
|
||||
},
|
||||
];
|
||||
testScenarios.forEach(({
|
||||
description, invalidValue,
|
||||
}) => {
|
||||
it(description, () => {
|
||||
const valueName = 'invalidValue';
|
||||
const expectedMessage = `'${valueName}' should be of type 'string', but is of type '${typeof invalidValue}'.`;
|
||||
const { assertNonEmptyString } = createTypeValidator();
|
||||
// act
|
||||
const act = () => assertNonEmptyString({ value: invalidValue, valueName });
|
||||
// assert
|
||||
expect(act).to.throw(expectedMessage);
|
||||
});
|
||||
});
|
||||
});
|
||||
it('throws an error if the string does not match the regex', () => {
|
||||
// arrange
|
||||
const regex: RegExp = /goodbye/;
|
||||
const stringNotMatchingRegex = 'Hello';
|
||||
const expectedMessage = 'String should contain "goodbye"';
|
||||
const rule: RegexValidationRule = {
|
||||
expectedMatch: regex,
|
||||
errorMessage: expectedMessage,
|
||||
};
|
||||
const assertion: NonEmptyStringAssertion = {
|
||||
value: stringNotMatchingRegex,
|
||||
valueName: 'non-important-value-name',
|
||||
rule,
|
||||
};
|
||||
const { assertNonEmptyString } = createTypeValidator();
|
||||
// act
|
||||
const act = () => assertNonEmptyString(assertion);
|
||||
// assert
|
||||
expect(act).to.throw(expectedMessage);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function createObjectWithProperties(properties: readonly string[]): object {
|
||||
|
||||
@@ -1,52 +1,110 @@
|
||||
import { describe, expect } from 'vitest';
|
||||
import { FunctionCallArgument } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
|
||||
import { itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
import { testParameterName } from '../../../ParameterNameTestRunner';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { createFunctionCallArgument } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
|
||||
import { TypeValidatorStub } from '@tests/unit/shared/Stubs/TypeValidatorStub';
|
||||
import type { NonEmptyStringAssertion, TypeValidator } from '@/application/Parser/Common/TypeValidator';
|
||||
import { createParameterNameValidatorStub } from '@tests/unit/shared/Stubs/ParameterNameValidatorStub';
|
||||
import type { ParameterNameValidator } from '@/application/Parser/Executable/Script/Compiler/Function/Shared/ParameterNameValidator';
|
||||
|
||||
describe('FunctionCallArgument', () => {
|
||||
describe('ctor', () => {
|
||||
describe('createFunctionCallArgument', () => {
|
||||
describe('parameter name', () => {
|
||||
testParameterName(
|
||||
(parameterName) => new FunctionCallArgumentBuilder()
|
||||
.withParameterName(parameterName)
|
||||
.build()
|
||||
.parameterName,
|
||||
);
|
||||
});
|
||||
describe('throws if argument value is absent', () => {
|
||||
itEachAbsentStringValue((absentValue) => {
|
||||
it('assigns correctly', () => {
|
||||
// arrange
|
||||
const parameterName = 'paramName';
|
||||
const expectedError = `Missing argument value for the parameter "${parameterName}".`;
|
||||
const argumentValue = absentValue;
|
||||
const expectedName = 'expected parameter name';
|
||||
const context = new TestContext()
|
||||
.withParameterName(expectedName);
|
||||
// act
|
||||
const act = () => new FunctionCallArgumentBuilder()
|
||||
.withParameterName(parameterName)
|
||||
.withArgumentValue(argumentValue)
|
||||
.build();
|
||||
const actualArgument = context.create();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
}, { excludeNull: true, excludeUndefined: true });
|
||||
const actualName = actualArgument.parameterName;
|
||||
expect(actualName).toEqual(expectedName);
|
||||
});
|
||||
it('validates parameter name', () => {
|
||||
// arrange
|
||||
const validator = createParameterNameValidatorStub();
|
||||
const expectedParameterName = 'parameter name expected to be validated';
|
||||
const context = new TestContext()
|
||||
.withParameterName(expectedParameterName)
|
||||
.withParameterNameValidator(validator.validator);
|
||||
// act
|
||||
context.create();
|
||||
// assert
|
||||
expect(validator.validatedNames).to.have.lengthOf(1);
|
||||
expect(validator.validatedNames).to.include(expectedParameterName);
|
||||
});
|
||||
});
|
||||
describe('argument value', () => {
|
||||
it('assigns correctly', () => {
|
||||
// arrange
|
||||
const expectedValue = 'expected argument value';
|
||||
const context = new TestContext()
|
||||
.withArgumentValue(expectedValue);
|
||||
// act
|
||||
const actualArgument = context.create();
|
||||
// assert
|
||||
const actualValue = actualArgument.argumentValue;
|
||||
expect(actualValue).toEqual(expectedValue);
|
||||
});
|
||||
it('validates argument value', () => {
|
||||
// arrange
|
||||
const parameterNameInError = 'expected parameter with argument error';
|
||||
const expectedArgumentValue = 'argument value to be validated';
|
||||
const expectedAssertion: NonEmptyStringAssertion = {
|
||||
value: expectedArgumentValue,
|
||||
valueName: `Missing argument value for the parameter "${parameterNameInError}".`,
|
||||
};
|
||||
const typeValidator = new TypeValidatorStub();
|
||||
const context = new TestContext()
|
||||
.withArgumentValue(expectedArgumentValue)
|
||||
.withParameterName(parameterNameInError)
|
||||
.withTypeValidator(typeValidator);
|
||||
// act
|
||||
context.create();
|
||||
// assert
|
||||
typeValidator.assertNonEmptyString(expectedAssertion);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class FunctionCallArgumentBuilder {
|
||||
private parameterName = 'default-parameter-name';
|
||||
class TestContext {
|
||||
private parameterName = `[${TestContext.name}] default-parameter-name`;
|
||||
|
||||
private argumentValue = 'default-argument-value';
|
||||
private argumentValue = `[${TestContext.name}] default-argument-value`;
|
||||
|
||||
public withParameterName(parameterName: string) {
|
||||
private typeValidator: TypeValidator = new TypeValidatorStub();
|
||||
|
||||
private parameterNameValidator
|
||||
: ParameterNameValidator = createParameterNameValidatorStub().validator;
|
||||
|
||||
public withParameterName(parameterName: string): this {
|
||||
this.parameterName = parameterName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withArgumentValue(argumentValue: string) {
|
||||
public withArgumentValue(argumentValue: string): this {
|
||||
this.argumentValue = argumentValue;
|
||||
return this;
|
||||
}
|
||||
|
||||
public build() {
|
||||
return new FunctionCallArgument(this.parameterName, this.argumentValue);
|
||||
public withTypeValidator(typeValidator: TypeValidator): this {
|
||||
this.typeValidator = typeValidator;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withParameterNameValidator(parameterNameValidator: ParameterNameValidator): this {
|
||||
this.parameterNameValidator = parameterNameValidator;
|
||||
return this;
|
||||
}
|
||||
|
||||
public create(): ReturnType<typeof createFunctionCallArgument> {
|
||||
return createFunctionCallArgument(
|
||||
this.parameterName,
|
||||
this.argumentValue,
|
||||
{
|
||||
typeValidator: this.typeValidator,
|
||||
validateParameterName: this.parameterNameValidator,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { describe, it, expect } from 'vitest';
|
||||
import { FunctionCallArgumentCollection } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection';
|
||||
import { FunctionCallArgumentStub } from '@tests/unit/shared/Stubs/FunctionCallArgumentStub';
|
||||
import { itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
import type { IFunctionCallArgument } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/IFunctionCallArgument';
|
||||
import type { FunctionCallArgument } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
|
||||
|
||||
describe('FunctionCallArgumentCollection', () => {
|
||||
describe('addArgument', () => {
|
||||
@@ -25,7 +25,7 @@ describe('FunctionCallArgumentCollection', () => {
|
||||
// arrange
|
||||
const testCases: ReadonlyArray<{
|
||||
readonly description: string;
|
||||
readonly args: readonly IFunctionCallArgument[];
|
||||
readonly args: readonly FunctionCallArgument[];
|
||||
readonly expectedParameterNames: string[];
|
||||
}> = [{
|
||||
description: 'no args',
|
||||
|
||||
@@ -14,6 +14,8 @@ import { SharedFunctionCollectionStub } from '@tests/unit/shared/Stubs/SharedFun
|
||||
import { itThrowsContextualError } from '@tests/unit/application/Parser/Common/ContextualErrorTester';
|
||||
import type { ErrorWithContextWrapper } from '@/application/Parser/Common/ContextualError';
|
||||
import { errorWithContextWrapperStub } from '@tests/unit/shared/Stubs/ErrorWithContextWrapperStub';
|
||||
import type { FunctionCallArgumentFactory } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
|
||||
import { FunctionCallArgumentFactoryStub } from '../../../../../../../../../../../shared/Stubs/FunctionCallArgumentFactoryStub';
|
||||
|
||||
describe('NestedFunctionArgumentCompiler', () => {
|
||||
describe('createCompiledNestedCall', () => {
|
||||
@@ -266,6 +268,9 @@ class NestedFunctionArgumentCompilerBuilder implements ArgumentCompiler {
|
||||
|
||||
private wrapError: ErrorWithContextWrapper = errorWithContextWrapperStub;
|
||||
|
||||
private callArgumentFactory
|
||||
: FunctionCallArgumentFactory = new FunctionCallArgumentFactoryStub().factory;
|
||||
|
||||
public withExpressionsCompiler(expressionsCompiler: IExpressionsCompiler): this {
|
||||
this.expressionsCompiler = expressionsCompiler;
|
||||
return this;
|
||||
@@ -292,10 +297,11 @@ class NestedFunctionArgumentCompilerBuilder implements ArgumentCompiler {
|
||||
}
|
||||
|
||||
public createCompiledNestedCall(): FunctionCall {
|
||||
const compiler = new NestedFunctionArgumentCompiler(
|
||||
this.expressionsCompiler,
|
||||
this.wrapError,
|
||||
);
|
||||
const compiler = new NestedFunctionArgumentCompiler({
|
||||
expressionsCompiler: this.expressionsCompiler,
|
||||
wrapError: this.wrapError,
|
||||
createCallArgument: this.callArgumentFactory,
|
||||
});
|
||||
return compiler.createCompiledNestedCall(
|
||||
this.nestedFunctionCall,
|
||||
this.parentFunctionCall,
|
||||
|
||||
@@ -3,8 +3,12 @@ import type { FunctionCallsData, FunctionCallData } from '@/application/collecti
|
||||
import { parseFunctionCalls } from '@/application/Parser/Executable/Script/Compiler/Function/Call/FunctionCallsParser';
|
||||
import { FunctionCallDataStub } from '@tests/unit/shared/Stubs/FunctionCallDataStub';
|
||||
import { itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
import type { NonEmptyCollectionAssertion, ObjectAssertion, TypeValidator } from '@/application/Parser/Common/TypeValidator';
|
||||
import type {
|
||||
NonEmptyCollectionAssertion, ObjectAssertion, TypeValidator,
|
||||
} from '@/application/Parser/Common/TypeValidator';
|
||||
import { TypeValidatorStub } from '@tests/unit/shared/Stubs/TypeValidatorStub';
|
||||
import type { FunctionCallArgumentFactory } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
|
||||
import { FunctionCallArgumentFactoryStub } from '@tests/unit/shared/Stubs/FunctionCallArgumentFactoryStub';
|
||||
|
||||
describe('FunctionCallsParser', () => {
|
||||
describe('parseFunctionCalls', () => {
|
||||
@@ -174,12 +178,15 @@ describe('FunctionCallsParser', () => {
|
||||
});
|
||||
|
||||
class TestContext {
|
||||
private validator: TypeValidator = new TypeValidatorStub();
|
||||
private typeValidator: TypeValidator = new TypeValidatorStub();
|
||||
|
||||
private createCallArgument
|
||||
: FunctionCallArgumentFactory = new FunctionCallArgumentFactoryStub().factory;
|
||||
|
||||
private calls: FunctionCallsData = [new FunctionCallDataStub()];
|
||||
|
||||
public withTypeValidator(typeValidator: TypeValidator): this {
|
||||
this.validator = typeValidator;
|
||||
this.typeValidator = typeValidator;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -191,7 +198,10 @@ class TestContext {
|
||||
public parse(): ReturnType<typeof parseFunctionCalls> {
|
||||
return parseFunctionCalls(
|
||||
this.calls,
|
||||
this.validator,
|
||||
{
|
||||
typeValidator: this.typeValidator,
|
||||
createCallArgument: this.createCallArgument,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { FunctionParameter } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameter';
|
||||
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);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import type { ParameterDefinitionData } from '@/application/collections/';
|
||||
import type { ParameterNameValidator } from '@/application/Parser/Executable/Script/Compiler/Function/Shared/ParameterNameValidator';
|
||||
import { createParameterNameValidatorStub } from '@tests/unit/shared/Stubs/ParameterNameValidatorStub';
|
||||
import { parseFunctionParameter } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameterParser';
|
||||
import { ParameterDefinitionDataStub } from '@tests/unit/shared/Stubs/ParameterDefinitionDataStub';
|
||||
|
||||
describe('FunctionParameterParser', () => {
|
||||
describe('parseFunctionParameter', () => {
|
||||
describe('name', () => {
|
||||
it('assigns correctly', () => {
|
||||
// arrange
|
||||
const expectedName = 'expected-function-name';
|
||||
const data = new ParameterDefinitionDataStub()
|
||||
.withName(expectedName);
|
||||
// act
|
||||
const actual = new TestContext()
|
||||
.withData(data)
|
||||
.parse();
|
||||
// expect
|
||||
const actualName = actual.name;
|
||||
expect(actualName).to.equal(expectedName);
|
||||
});
|
||||
it('validates correctly', () => {
|
||||
// arrange
|
||||
const expectedName = 'expected-function-name';
|
||||
const { validator, validatedNames } = createParameterNameValidatorStub();
|
||||
const data = new ParameterDefinitionDataStub()
|
||||
.withName(expectedName);
|
||||
// act
|
||||
new TestContext()
|
||||
.withData(data)
|
||||
.withValidator(validator)
|
||||
.parse();
|
||||
// expect
|
||||
expect(validatedNames).to.have.lengthOf(1);
|
||||
expect(validatedNames).to.contain(expectedName);
|
||||
});
|
||||
});
|
||||
describe('isOptional', () => {
|
||||
describe('assigns correctly', () => {
|
||||
// arrange
|
||||
const expectedValues = [true, false];
|
||||
for (const expected of expectedValues) {
|
||||
it(expected.toString(), () => {
|
||||
const data = new ParameterDefinitionDataStub()
|
||||
.withOptionality(expected);
|
||||
// act
|
||||
const actual = new TestContext()
|
||||
.withData(data)
|
||||
.parse();
|
||||
// expect
|
||||
expect(actual.isOptional).to.equal(expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class TestContext {
|
||||
private data: ParameterDefinitionData = new ParameterDefinitionDataStub()
|
||||
.withName(`[${TestContext.name}]function-name`);
|
||||
|
||||
private validator: ParameterNameValidator = createParameterNameValidatorStub().validator;
|
||||
|
||||
public withData(data: ParameterDefinitionData) {
|
||||
this.data = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withValidator(parameterNameValidator: ParameterNameValidator): this {
|
||||
this.validator = parameterNameValidator;
|
||||
return this;
|
||||
}
|
||||
|
||||
public parse() {
|
||||
return parseFunctionParameter(
|
||||
this.data,
|
||||
this.validator,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import type {
|
||||
ParameterDefinitionData, FunctionCallsData,
|
||||
} from '@/application/collections/';
|
||||
import type { ISharedFunction } from '@/application/Parser/Executable/Script/Compiler/Function/ISharedFunction';
|
||||
import { parseSharedFunctions, type FunctionParameterFactory } from '@/application/Parser/Executable/Script/Compiler/Function/SharedFunctionsParser';
|
||||
import { parseSharedFunctions } from '@/application/Parser/Executable/Script/Compiler/Function/SharedFunctionsParser';
|
||||
import { createFunctionDataWithCall, createFunctionDataWithCode, createFunctionDataWithoutCallOrCode } from '@tests/unit/shared/Stubs/FunctionDataStub';
|
||||
import { ParameterDefinitionDataStub } from '@tests/unit/shared/Stubs/ParameterDefinitionDataStub';
|
||||
import { FunctionCallDataStub } from '@tests/unit/shared/Stubs/FunctionCallDataStub';
|
||||
@@ -16,7 +16,6 @@ import type { ICodeValidator } from '@/application/Parser/Executable/Script/Vali
|
||||
import { NoEmptyLines } from '@/application/Parser/Executable/Script/Validation/Rules/NoEmptyLines';
|
||||
import { NoDuplicatedLines } from '@/application/Parser/Executable/Script/Validation/Rules/NoDuplicatedLines';
|
||||
import type { ErrorWithContextWrapper } from '@/application/Parser/Common/ContextualError';
|
||||
import { FunctionParameterStub } from '@tests/unit/shared/Stubs/FunctionParameterStub';
|
||||
import { errorWithContextWrapperStub } from '@tests/unit/shared/Stubs/ErrorWithContextWrapperStub';
|
||||
import { itThrowsContextualError } from '@tests/unit/application/Parser/Common/ContextualErrorTester';
|
||||
import type { FunctionParameterCollectionFactory } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameterCollectionFactory';
|
||||
@@ -26,6 +25,8 @@ import { createFunctionCallsParserStub } from '@tests/unit/shared/Stubs/Function
|
||||
import { FunctionCallStub } from '@tests/unit/shared/Stubs/FunctionCallStub';
|
||||
import type { IReadOnlyFunctionParameterCollection } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/IFunctionParameterCollection';
|
||||
import type { FunctionCall } from '@/application/Parser/Executable/Script/Compiler/Function/Call/FunctionCall';
|
||||
import type { FunctionParameterParser } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameterParser';
|
||||
import { createFunctionParameterParserStub } from '@tests/unit/shared/Stubs/FunctionParameterParserStub';
|
||||
import { expectCallsFunctionBody, expectCodeFunctionBody } from './ExpectFunctionBodyType';
|
||||
|
||||
describe('SharedFunctionsParser', () => {
|
||||
@@ -185,7 +186,7 @@ describe('SharedFunctionsParser', () => {
|
||||
const functionName = 'functionName';
|
||||
const expectedErrorMessage = `Failed to create parameter: ${invalidParameterName} for function "${functionName}"`;
|
||||
const expectedInnerError = new Error('injected error');
|
||||
const parameterFactory: FunctionParameterFactory = () => {
|
||||
const parser: FunctionParameterParser = () => {
|
||||
throw expectedInnerError;
|
||||
};
|
||||
const functionData = createFunctionDataWithCode()
|
||||
@@ -196,7 +197,7 @@ describe('SharedFunctionsParser', () => {
|
||||
throwingAction: (wrapError) => {
|
||||
new TestContext()
|
||||
.withFunctions([functionData])
|
||||
.withFunctionParameterFactory(parameterFactory)
|
||||
.withFunctionParameterParser(parser)
|
||||
.withErrorWrapper(wrapError)
|
||||
.parseFunctions();
|
||||
},
|
||||
@@ -415,12 +416,7 @@ class TestContext {
|
||||
|
||||
private functionCallsParser: FunctionCallsParser = createFunctionCallsParserStub().parser;
|
||||
|
||||
private parameterFactory: FunctionParameterFactory = (
|
||||
name: string,
|
||||
isOptional: boolean,
|
||||
) => new FunctionParameterStub()
|
||||
.withName(name)
|
||||
.withOptional(isOptional);
|
||||
private functionParameterParser: FunctionParameterParser = createFunctionParameterParserStub;
|
||||
|
||||
private parameterCollectionFactory
|
||||
: FunctionParameterCollectionFactory = () => new FunctionParameterCollectionStub();
|
||||
@@ -450,8 +446,8 @@ class TestContext {
|
||||
return this;
|
||||
}
|
||||
|
||||
public withFunctionParameterFactory(parameterFactory: FunctionParameterFactory): this {
|
||||
this.parameterFactory = parameterFactory;
|
||||
public withFunctionParameterParser(functionParameterParser: FunctionParameterParser): this {
|
||||
this.functionParameterParser = functionParameterParser;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -469,7 +465,7 @@ class TestContext {
|
||||
{
|
||||
codeValidator: this.codeValidator,
|
||||
wrapError: this.wrapError,
|
||||
createParameter: this.parameterFactory,
|
||||
parseParameter: this.functionParameterParser,
|
||||
createParameterCollection: this.parameterCollectionFactory,
|
||||
parseFunctionCalls: this.functionCallsParser,
|
||||
},
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
export function testParameterName(action: (parameterName: string) => string) {
|
||||
describe('name', () => {
|
||||
describe('sets as expected', () => {
|
||||
// arrange
|
||||
const expectedValues: readonly string[] = [
|
||||
'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 testScenarios: readonly {
|
||||
readonly description: string;
|
||||
readonly value: string;
|
||||
readonly expectedError: string;
|
||||
}[] = [
|
||||
{
|
||||
description: 'empty Name',
|
||||
value: '',
|
||||
expectedError: 'missing parameter name',
|
||||
},
|
||||
{
|
||||
description: 'has @',
|
||||
value: 'b@d',
|
||||
expectedError: 'parameter name must be alphanumeric but it was "b@d"',
|
||||
},
|
||||
{
|
||||
description: 'has {',
|
||||
value: 'b{a}d',
|
||||
expectedError: 'parameter name must be alphanumeric but it was "b{a}d"',
|
||||
},
|
||||
];
|
||||
for (const { description, value, expectedError } of testScenarios) {
|
||||
it(description, () => {
|
||||
// act
|
||||
const act = () => action(value);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { describe, it } from 'vitest';
|
||||
import { TypeValidatorStub } from '@tests/unit/shared/Stubs/TypeValidatorStub';
|
||||
import { validateParameterName } from '@/application/Parser/Executable/Script/Compiler/Function/Shared/ParameterNameValidator';
|
||||
import type { NonEmptyStringAssertion } from '@/application/Parser/Common/TypeValidator';
|
||||
|
||||
describe('ParameterNameValidator', () => {
|
||||
it('asserts correctly', () => {
|
||||
// arrange
|
||||
const parameterName = 'expected-parameter-name';
|
||||
const validator = new TypeValidatorStub();
|
||||
const expectedAssertion: NonEmptyStringAssertion = {
|
||||
value: parameterName,
|
||||
valueName: 'parameter name',
|
||||
rule: {
|
||||
expectedMatch: /^[0-9a-zA-Z]+$/,
|
||||
errorMessage: `parameter name must be alphanumeric but it was "${parameterName}".`,
|
||||
},
|
||||
};
|
||||
// act
|
||||
validateParameterName(parameterName, validator);
|
||||
// assert
|
||||
validator.assertNonEmptyString(expectedAssertion);
|
||||
});
|
||||
});
|
||||
@@ -1,91 +1,120 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { CodeSubstituter } from '@/application/Parser/ScriptingDefinition/CodeSubstituter';
|
||||
import type { IExpressionsCompiler } from '@/application/Parser/Executable/Script/Compiler/Expressions/IExpressionsCompiler';
|
||||
import { ProjectDetailsStub } from '@tests/unit/shared/Stubs/ProjectDetailsStub';
|
||||
import { ExpressionsCompilerStub } from '@tests/unit/shared/Stubs/ExpressionsCompilerStub';
|
||||
import { itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||
import { substituteCode } from '@/application/Parser/ScriptingDefinition/CodeSubstituter';
|
||||
import type { ProjectDetails } from '@/domain/Project/ProjectDetails';
|
||||
import type { FunctionCallArgumentFactory } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
|
||||
import { FunctionCallArgumentFactoryStub } from '@tests/unit/shared/Stubs/FunctionCallArgumentFactoryStub';
|
||||
|
||||
describe('CodeSubstituter', () => {
|
||||
describe('throws if code is empty', () => {
|
||||
itEachAbsentStringValue((emptyCode) => {
|
||||
// arrange
|
||||
const expectedError = 'missing code';
|
||||
const code = emptyCode;
|
||||
const projectDetails = new ProjectDetailsStub();
|
||||
const sut = new CodeSubstituterBuilder().build();
|
||||
// act
|
||||
const act = () => sut.substitute(code, projectDetails);
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
}, { excludeNull: true, excludeUndefined: true });
|
||||
});
|
||||
describe('substitutes parameters as expected values', () => {
|
||||
// arrange
|
||||
const projectDetails = new ProjectDetailsStub();
|
||||
const date = new Date();
|
||||
const testCases: Array<{ parameter: string, argument: string }> = [
|
||||
{
|
||||
parameter: 'homepage',
|
||||
argument: projectDetails.homepage,
|
||||
},
|
||||
{
|
||||
parameter: 'version',
|
||||
argument: projectDetails.version.toString(),
|
||||
},
|
||||
{
|
||||
parameter: 'date',
|
||||
argument: date.toUTCString(),
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(`substitutes ${testCase.parameter} as expected`, () => {
|
||||
const compilerStub = new ExpressionsCompilerStub();
|
||||
const sut = new CodeSubstituterBuilder()
|
||||
.withCompiler(compilerStub)
|
||||
.withDate(date)
|
||||
.build();
|
||||
describe('substituteCode', () => {
|
||||
describe('throws if code is empty', () => {
|
||||
itEachAbsentStringValue((emptyCode) => {
|
||||
// arrange
|
||||
const expectedError = 'missing code';
|
||||
const context = new TestContext()
|
||||
.withCode(emptyCode);
|
||||
// act
|
||||
sut.substitute('non empty code', projectDetails);
|
||||
const act = () => context.substitute();
|
||||
// assert
|
||||
expect(compilerStub.callHistory).to.have.lengthOf(1);
|
||||
const parameters = compilerStub.callHistory[0].args[1];
|
||||
expect(parameters.hasArgument(testCase.parameter));
|
||||
const { argumentValue } = parameters.getArgument(testCase.parameter);
|
||||
expect(argumentValue).to.equal(testCase.argument);
|
||||
});
|
||||
}
|
||||
});
|
||||
it('returns code as it is', () => {
|
||||
// arrange
|
||||
const expected = 'expected-code';
|
||||
const compilerStub = new ExpressionsCompilerStub();
|
||||
const sut = new CodeSubstituterBuilder()
|
||||
.withCompiler(compilerStub)
|
||||
.build();
|
||||
// act
|
||||
sut.substitute(expected, new ProjectDetailsStub());
|
||||
// assert
|
||||
expect(compilerStub.callHistory).to.have.lengthOf(1);
|
||||
expect(compilerStub.callHistory[0].args[0]).to.equal(expected);
|
||||
expect(act).to.throw(expectedError);
|
||||
}, { excludeNull: true, excludeUndefined: true });
|
||||
});
|
||||
describe('substitutes parameters as expected values', () => {
|
||||
// arrange
|
||||
const projectDetails = new ProjectDetailsStub();
|
||||
const date = new Date();
|
||||
const testCases: Array<{ parameter: string, argument: string }> = [
|
||||
{
|
||||
parameter: 'homepage',
|
||||
argument: projectDetails.homepage,
|
||||
},
|
||||
{
|
||||
parameter: 'version',
|
||||
argument: projectDetails.version.toString(),
|
||||
},
|
||||
{
|
||||
parameter: 'date',
|
||||
argument: date.toUTCString(),
|
||||
},
|
||||
];
|
||||
for (const testCase of testCases) {
|
||||
it(`substitutes ${testCase.parameter} as expected`, () => {
|
||||
const compilerStub = new ExpressionsCompilerStub();
|
||||
const context = new TestContext()
|
||||
.withCompiler(compilerStub)
|
||||
.withDate(date)
|
||||
.withProjectDetails(projectDetails);
|
||||
// act
|
||||
context.substitute();
|
||||
// assert
|
||||
expect(compilerStub.callHistory).to.have.lengthOf(1);
|
||||
const parameters = compilerStub.callHistory[0].args[1];
|
||||
expect(parameters.hasArgument(testCase.parameter));
|
||||
const { argumentValue } = parameters.getArgument(testCase.parameter);
|
||||
expect(argumentValue).to.equal(testCase.argument);
|
||||
});
|
||||
}
|
||||
});
|
||||
it('returns code as it is', () => {
|
||||
// arrange
|
||||
const expected = 'expected-code';
|
||||
const compilerStub = new ExpressionsCompilerStub();
|
||||
const context = new TestContext()
|
||||
.withCompiler(compilerStub)
|
||||
.withCode(expected);
|
||||
// act
|
||||
context.substitute();
|
||||
// assert
|
||||
expect(compilerStub.callHistory).to.have.lengthOf(1);
|
||||
expect(compilerStub.callHistory[0].args[0]).to.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class CodeSubstituterBuilder {
|
||||
class TestContext {
|
||||
private compiler: IExpressionsCompiler = new ExpressionsCompilerStub();
|
||||
|
||||
private date = new Date();
|
||||
|
||||
public withCompiler(compiler: IExpressionsCompiler) {
|
||||
private code = `[${TestContext.name}] default code for testing`;
|
||||
|
||||
private projectDetails: ProjectDetails = new ProjectDetailsStub();
|
||||
|
||||
private callArgumentFactory
|
||||
: FunctionCallArgumentFactory = new FunctionCallArgumentFactoryStub().factory;
|
||||
|
||||
public withCompiler(compiler: IExpressionsCompiler): this {
|
||||
this.compiler = compiler;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withDate(date: Date) {
|
||||
public withDate(date: Date): this {
|
||||
this.date = date;
|
||||
return this;
|
||||
}
|
||||
|
||||
public build() {
|
||||
return new CodeSubstituter(this.compiler, this.date);
|
||||
public withCode(code: string): this {
|
||||
this.code = code;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withProjectDetails(projectDetails: ProjectDetails): this {
|
||||
this.projectDetails = projectDetails;
|
||||
return this;
|
||||
}
|
||||
|
||||
public substitute(): ReturnType<typeof substituteCode> {
|
||||
return substituteCode(
|
||||
this.code,
|
||||
this.projectDetails,
|
||||
{
|
||||
compiler: this.compiler,
|
||||
provideDate: () => this.date,
|
||||
createCallArgument: this.callArgumentFactory,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||
import type { EnumParser } from '@/application/Common/Enum';
|
||||
import type { ICodeSubstituter } from '@/application/Parser/ScriptingDefinition/ICodeSubstituter';
|
||||
import type { CodeSubstituter } from '@/application/Parser/ScriptingDefinition/CodeSubstituter';
|
||||
import type { IScriptingDefinition } from '@/domain/IScriptingDefinition';
|
||||
import { ProjectDetailsStub } from '@tests/unit/shared/Stubs/ProjectDetailsStub';
|
||||
import { EnumParserStub } from '@tests/unit/shared/Stubs/EnumParserStub';
|
||||
@@ -83,7 +83,7 @@ describe('ScriptingDefinitionParser', () => {
|
||||
const context = new TestContext()
|
||||
.withData(data)
|
||||
.withProjectDetails(projectDetails)
|
||||
.withSubstituter(substituterMock);
|
||||
.withSubstituter(substituterMock.substitute);
|
||||
// act
|
||||
const definition = context.parseScriptingDefinition();
|
||||
// assert
|
||||
@@ -99,7 +99,7 @@ class TestContext {
|
||||
private languageParser: EnumParser<ScriptingLanguage> = new EnumParserStub<ScriptingLanguage>()
|
||||
.setupDefaultValue(ScriptingLanguage.shellscript);
|
||||
|
||||
private codeSubstituter: ICodeSubstituter = new CodeSubstituterStub();
|
||||
private codeSubstituter: CodeSubstituter = new CodeSubstituterStub().substitute;
|
||||
|
||||
private validator: TypeValidator = new TypeValidatorStub();
|
||||
|
||||
@@ -122,7 +122,7 @@ class TestContext {
|
||||
return this;
|
||||
}
|
||||
|
||||
public withSubstituter(substituter: ICodeSubstituter): this {
|
||||
public withSubstituter(substituter: CodeSubstituter): this {
|
||||
this.codeSubstituter = substituter;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import type { ProjectDetails } from '@/domain/Project/ProjectDetails';
|
||||
import type { ICodeSubstituter } from '@/application/Parser/ScriptingDefinition/ICodeSubstituter';
|
||||
import type { CodeSubstituter } from '@/application/Parser/ScriptingDefinition/CodeSubstituter';
|
||||
|
||||
export class CodeSubstituterStub implements ICodeSubstituter {
|
||||
export class CodeSubstituterStub {
|
||||
private readonly scenarios = new Array<{
|
||||
code: string, projectDetails: ProjectDetails, result: string }>();
|
||||
|
||||
public substitute(code: string, projectDetails: ProjectDetails): string {
|
||||
public setup(code: string, projectDetails: ProjectDetails, result: string) {
|
||||
this.scenarios.push({ code, projectDetails, result });
|
||||
return this;
|
||||
}
|
||||
|
||||
public substitute: CodeSubstituter = (code: string, projectDetails: ProjectDetails) => {
|
||||
const scenario = this.scenarios.find(
|
||||
(s) => s.code === code && s.projectDetails === projectDetails,
|
||||
);
|
||||
if (scenario) {
|
||||
return scenario.result;
|
||||
}
|
||||
return `[CodeSubstituterStub] - code: ${code}`;
|
||||
}
|
||||
|
||||
public setup(code: string, projectDetails: ProjectDetails, result: string) {
|
||||
this.scenarios.push({ code, projectDetails, result });
|
||||
return this;
|
||||
}
|
||||
return `[${CodeSubstituterStub.name}] - code: ${code}`;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { IFunctionCallArgument } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/IFunctionCallArgument';
|
||||
import type { FunctionCallArgument } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
|
||||
import type { IFunctionCallArgumentCollection } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/IFunctionCallArgumentCollection';
|
||||
import { FunctionCallArgumentStub } from './FunctionCallArgumentStub';
|
||||
|
||||
export class FunctionCallArgumentCollectionStub implements IFunctionCallArgumentCollection {
|
||||
private args = new Array<IFunctionCallArgument>();
|
||||
private args = new Array<FunctionCallArgument>();
|
||||
|
||||
public withEmptyArguments(): this {
|
||||
this.args.length = 0;
|
||||
@@ -36,7 +36,7 @@ export class FunctionCallArgumentCollectionStub implements IFunctionCallArgument
|
||||
return this.args.some((a) => a.parameterName === parameterName);
|
||||
}
|
||||
|
||||
public addArgument(argument: IFunctionCallArgument): void {
|
||||
public addArgument(argument: FunctionCallArgument): void {
|
||||
this.args.push(argument);
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ export class FunctionCallArgumentCollectionStub implements IFunctionCallArgument
|
||||
return this.args.map((a) => a.parameterName);
|
||||
}
|
||||
|
||||
public getArgument(parameterName: string): IFunctionCallArgument {
|
||||
public getArgument(parameterName: string): FunctionCallArgument {
|
||||
const arg = this.args.find((a) => a.parameterName === parameterName);
|
||||
if (!arg) {
|
||||
throw new Error(`no argument exists for parameter "${parameterName}"`);
|
||||
|
||||
10
tests/unit/shared/Stubs/FunctionCallArgumentFactoryStub.ts
Normal file
10
tests/unit/shared/Stubs/FunctionCallArgumentFactoryStub.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { FunctionCallArgumentFactory } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
|
||||
import { FunctionCallArgumentStub } from './FunctionCallArgumentStub';
|
||||
|
||||
export class FunctionCallArgumentFactoryStub {
|
||||
public factory: FunctionCallArgumentFactory = (parameterName, argumentValue) => {
|
||||
return new FunctionCallArgumentStub()
|
||||
.withParameterName(parameterName)
|
||||
.withArgumentValue(argumentValue);
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { IFunctionCallArgument } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/IFunctionCallArgument';
|
||||
import type { FunctionCallArgument } from '@/application/Parser/Executable/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
|
||||
|
||||
export class FunctionCallArgumentStub implements IFunctionCallArgument {
|
||||
export class FunctionCallArgumentStub implements FunctionCallArgument {
|
||||
public parameterName = 'stub-parameter-name';
|
||||
|
||||
public argumentValue = 'stub-arg-name';
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import type { IFunctionParameter } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/IFunctionParameter';
|
||||
import type { IFunctionParameterCollection } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/IFunctionParameterCollection';
|
||||
import type { FunctionParameter } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameter';
|
||||
import { FunctionParameterStub } from './FunctionParameterStub';
|
||||
|
||||
export class FunctionParameterCollectionStub implements IFunctionParameterCollection {
|
||||
private parameters = new Array<IFunctionParameter>();
|
||||
private parameters = new Array<FunctionParameter>();
|
||||
|
||||
public addParameter(parameter: IFunctionParameter): void {
|
||||
public addParameter(parameter: FunctionParameter): void {
|
||||
this.parameters.push(parameter);
|
||||
}
|
||||
|
||||
public get all(): readonly IFunctionParameter[] {
|
||||
public get all(): readonly FunctionParameter[] {
|
||||
return this.parameters;
|
||||
}
|
||||
|
||||
|
||||
8
tests/unit/shared/Stubs/FunctionParameterParserStub.ts
Normal file
8
tests/unit/shared/Stubs/FunctionParameterParserStub.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { FunctionParameterParser } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameterParser';
|
||||
import { FunctionParameterStub } from './FunctionParameterStub';
|
||||
|
||||
export const createFunctionParameterParserStub: FunctionParameterParser = (parameters) => {
|
||||
return new FunctionParameterStub()
|
||||
.withName(parameters.name)
|
||||
.withOptional(parameters.optional || false);
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { IFunctionParameter } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/IFunctionParameter';
|
||||
import type { FunctionParameter } from '@/application/Parser/Executable/Script/Compiler/Function/Parameter/FunctionParameter';
|
||||
|
||||
export class FunctionParameterStub implements IFunctionParameter {
|
||||
export class FunctionParameterStub implements FunctionParameter {
|
||||
public name = 'function-parameter-stub';
|
||||
|
||||
public isOptional = true;
|
||||
|
||||
12
tests/unit/shared/Stubs/ParameterNameValidatorStub.ts
Normal file
12
tests/unit/shared/Stubs/ParameterNameValidatorStub.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { ParameterNameValidator } from '@/application/Parser/Executable/Script/Compiler/Function/Shared/ParameterNameValidator';
|
||||
|
||||
export const createParameterNameValidatorStub = () => {
|
||||
const validatedNames = new Array<string>();
|
||||
const validator: ParameterNameValidator = (name) => {
|
||||
validatedNames.push(name);
|
||||
};
|
||||
return {
|
||||
validator,
|
||||
validatedNames,
|
||||
};
|
||||
};
|
||||
@@ -1,4 +1,7 @@
|
||||
import type { NonEmptyCollectionAssertion, ObjectAssertion, TypeValidator } from '@/application/Parser/Common/TypeValidator';
|
||||
import type {
|
||||
NonEmptyCollectionAssertion, NonEmptyStringAssertion,
|
||||
ObjectAssertion, TypeValidator,
|
||||
} from '@/application/Parser/Common/TypeValidator';
|
||||
import type { FunctionKeys } from '@/TypeHelpers';
|
||||
import { expectDeepIncludes } from '@tests/shared/Assertions/ExpectDeepIncludes';
|
||||
import { StubWithObservableMethodCalls } from './StubWithObservableMethodCalls';
|
||||
@@ -22,6 +25,13 @@ export class TypeValidatorStub
|
||||
});
|
||||
}
|
||||
|
||||
public assertNonEmptyString(assertion: NonEmptyStringAssertion): void {
|
||||
this.registerMethodCall({
|
||||
methodName: 'assertNonEmptyString',
|
||||
args: [assertion],
|
||||
});
|
||||
}
|
||||
|
||||
public expectObjectAssertion<T>(
|
||||
expectedAssertion: ObjectAssertion<T>,
|
||||
): void {
|
||||
@@ -34,6 +44,12 @@ export class TypeValidatorStub
|
||||
this.expectAssertion('assertNonEmptyCollection', expectedAssertion);
|
||||
}
|
||||
|
||||
public expectNonEmptyStringAssertion(
|
||||
expectedAssertion: NonEmptyStringAssertion,
|
||||
): void {
|
||||
this.expectAssertion('assertNonEmptyString', expectedAssertion);
|
||||
}
|
||||
|
||||
private expectAssertion<T extends FunctionKeys<TypeValidator>>(
|
||||
methodName: T,
|
||||
expectedAssertion: Parameters<TypeValidator[T]>[0],
|
||||
|
||||
Reference in New Issue
Block a user