import 'mocha' ; import { expect } from 'chai' ; import { ExpressionPosition } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionPosition' ; import { ExpressionEvaluator , Expression } from '@/application/Parser/Script/Compiler/Expressions/Expression/Expression' ; import { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/Function/Call/Argument/IFunctionCallArgumentCollection' ; import { FunctionCallArgumentCollectionStub } from '@tests/unit/stubs/FunctionCallArgumentCollectionStub' ; import { FunctionParameterCollectionStub } from '@tests/unit/stubs/FunctionParameterCollectionStub' ; import { FunctionCallArgumentStub } from '@tests/unit/stubs/FunctionCallArgumentStub' ; import { ExpressionEvaluationContextStub } from '@tests/unit/stubs/ExpressionEvaluationContextStub' ; import { IPipelineCompiler } from '@/application/Parser/Script/Compiler/Expressions/Pipes/IPipelineCompiler' ; import { PipelineCompilerStub } from '@tests/unit/stubs/PipelineCompilerStub' ; import { IReadOnlyFunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/IFunctionParameterCollection' ; describe ( 'Expression' , ( ) => { describe ( 'ctor' , ( ) => { describe ( 'position' , ( ) => { it ( 'throws if undefined' , ( ) => { const expectedError = 'undefined position' ; const position = undefined ; const act = ( ) => new ExpressionBuilder ( ) . withPosition ( position ) . build ( ) ; expect ( act ) . to . throw ( expectedError ) ; } ) ; it ( 'sets as expected' , ( ) => { const expected = new ExpressionPosition ( 0 , 5 ) ; const actual = new ExpressionBuilder ( ) . withPosition ( expected ) . build ( ) ; expect ( actual . position ) . to . equal ( expected ) ; } ) ; } ) ; describe ( 'parameters' , ( ) => { it ( 'defaults to empty array if undefined' , ( ) => { const parameters = undefined ; const actual = new ExpressionBuilder ( ) . withParameters ( parameters ) . build ( ) ; expect ( actual . parameters ) ; expect ( actual . parameters . all ) ; expect ( actual . parameters . all . length ) . to . equal ( 0 ) ; } ) ; it ( 'sets as expected' , ( ) => { const expected = new FunctionParameterCollectionStub ( ) . withParameterName ( 'firstParameterName' ) . withParameterName ( 'secondParameterName' ) ; const actual = new ExpressionBuilder ( ) . withParameters ( expected ) . build ( ) ; expect ( actual . parameters ) . to . deep . equal ( expected ) ; } ) ; } ) ; describe ( 'evaluator' , ( ) => { it ( 'throws if undefined' , ( ) => { const expectedError = 'undefined evaluator' ; const evaluator = undefined ; const act = ( ) => new ExpressionBuilder ( ) . withEvaluator ( evaluator ) . build ( ) ; expect ( act ) . to . throw ( expectedError ) ; } ) ; } ) ; } ) ; describe ( 'evaluate' , ( ) => { describe ( 'throws with invalid arguments' , ( ) => { const testCases = [ { name : 'throws if arguments is undefined' , context : undefined , expectedError : 'undefined context' , } , { name : 'throws when some of the required args are not provided' , sut : ( i ) => 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' , sut : ( i ) => 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 , ( ) => { const sutBuilder = new ExpressionBuilder ( ) ; if ( testCase . sut ) { testCase . sut ( sutBuilder ) ; } const sut = sutBuilder . build ( ) ; const act = ( ) => sut . evaluate ( testCase . context ) ; expect ( act ) . to . throw ( testCase . expectedError ) ; } ) ; } } ) ; it ( 'returns result from evaluator' , ( ) => { const evaluatorMock = ( 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 ( ) ; const actual = sut . evaluate ( context ) ; expect ( expected ) . to . equal ( actual , printMessage ( ) ) ; function printMessage ( ) { return `\nGiven arguments: ${ JSON . stringify ( givenArguments ) }\n` + `\nExpected parameter names: ${ JSON . stringify ( expectedParameterNames ) }\n` ; } } ) ; it ( 'sends pipeline compiler as it is' , ( ) => { const expected = new PipelineCompilerStub ( ) ; const context = new ExpressionEvaluationContextStub ( ) . withPipelineCompiler ( expected ) ; let actual ; const evaluatorMock = ( c ) => { actual = c . pipelineCompiler ; return '' ; } ; const sut = new ExpressionBuilder ( ) . withEvaluator ( evaluatorMock ) . build ( ) ; sut . evaluate ( context ) ; expect ( expected ) . to . equal ( actual ) ; } ) ; describe ( 'filters unused parameters' , ( ) => { 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 ; const evaluatorMock = ( c ) => { actual = c . args ; return '' ; } ; const context = new ExpressionEvaluationContextStub ( ) . withArgs ( testCase . arguments ) ; const sut = new ExpressionBuilder ( ) . withEvaluator ( evaluatorMock ) . withParameters ( testCase . expressionParameters ) . build ( ) ; sut . evaluate ( context ) ; const actualArguments = actual . getAllParameterNames ( ) . map ( ( name ) => actual . 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 ) { this . position = position ; return this ; } public withEvaluator ( evaluator ) { this . evaluator = evaluator ; return this ; } public withParameters ( parameters ) { this . parameters = parameters ; return this ; } public withParameterName ( parameterName , isOptional = true ) { const collection = new FunctionParameterCollectionStub ( ) . withParameterName ( parameterName , isOptional ) ; return this . withParameters ( collection ) ; } public withParameterNames ( parameterNames , isOptional = true ) { const collection = new FunctionParameterCollectionStub ( ) . withParameterNames ( parameterNames , isOptional ) ; return this . withParameters ( collection ) ; } public build ( ) { return new Expression ( this . position , this . evaluator , this . parameters ) ; } private evaluator : ExpressionEvaluator = ( ) => '' ; }