diff --git a/platform/src/EducationPlatformApp.js b/platform/src/EducationPlatformApp.js index dca1070..b453e93 100644 --- a/platform/src/EducationPlatformApp.js +++ b/platform/src/EducationPlatformApp.js @@ -32,10 +32,11 @@ import { Button } from './Button.js'; import { Preloader } from './Preloader.js'; import { Layout } from './Layout.js'; import { PlaygroundUtility } from './PlaygroundUtility.js'; -import { jsonRequest, jsonRequestConversion, ARRAY_ANY_ELEMENT, urlParamPrivateRepo, utility } from './Utility.js'; +import { jsonRequest, urlParamPrivateRepo, utility } from './Utility.js'; const COMMON_UTILITY_URL = utility.getWindowLocationHref().replace( utility.getWindowLocationSearch(), "" ) + "common/utility.json"; +const ACTION_FUNCTION_LANGUAGE_TYPE = "text"; class EducationPlatformApp { outputType; @@ -151,7 +152,7 @@ class EducationPlatformApp { if (errors.length==0){ // An activity configuration has been provided - this.toolsManager = new ToolsManager(); + this.toolsManager = new ToolsManager(this.errorNotification); this.activityManager = new ActivityManager( (this.toolsManager.getPanelDefinition).bind(this.toolsManager), this.fileHandler ); this.activityManager.initializeActivities(); errors = errors.concat(this.activityManager.getConfigErrors()); @@ -419,98 +420,7 @@ class EducationPlatformApp { return $("#" + panelId)[0].dataset.titleCaption; } - /** - * Invokes an action function by placing requests to all the required external tool functions - * - * TODO: To be moved to the ToolManager issue #40 - * - * @param {string} functionId the id of tool function - * @param {Map} parameterMap map from parameter name to its value and type - * @returns {Promise} promise to result of the action function - */ - invokeActionFunction(functionId, parameterMap){ - - let actionFunction = this.toolsManager.functionRegistry_resolve(functionId); - - let parameterPromises = []; - - for ( const paramName of parameterMap.keys() ){ /* TODO add defensive checks that every required value - is provided issue #57 */ - - let actionFunctionParam = actionFunction.getParameters().find( p => p.name === paramName); - - /* Check the given parameter types against the those of the requested action function. - If required, request conversion from available tool functions */ - let givenParameter = parameterMap.get(paramName); - - if (givenParameter.type != actionFunctionParam.type){ - //Types don't match so try and convert - let convertedValue; - - const metamodelId = actionFunction.getInstanceOfParamName(paramName); - - if(metamodelId==null){ - // Convert with no metamodel to consider - convertedValue = this.convert( givenParameter.value, givenParameter.type, - actionFunctionParam.type, paramName ); // TODO issue #58 remove paramName - - } else { - // Convert considering metamodel - const givenMetamodel = parameterMap.get(metamodelId); - - convertedValue = this.convertIncludingMetamodel( givenParameter.value , givenParameter.type, - givenMetamodel.value, givenMetamodel.type, - actionFunctionParam.type, paramName ); // TODO issue #58 remove paramName - } - - parameterPromises.push(convertedValue); - - } else { - // Matching types add values to promise for synchronisation - let value = new Promise( function (resolve) { - let parameterData = {}; - - parameterData.name = paramName; - parameterData.data = givenParameter.value; - - resolve(parameterData); - }); - - parameterPromises.push(value); - } - } - - // Invoke the actionFunction on completion of any conversions - let actionFunctionPromise = new Promise( (resolve, reject) => { - - Promise.all( parameterPromises ).then( (values) => { - let actionRequestData = {}; - - //Populate the transformed parameters - for ( const param of actionFunction.getParameters() ){ - - const panelConfig = parameterMap.get(param.name); - - if (panelConfig != undefined){ - let parameterData = values.find(val => (val.name === param.name) ); - - actionRequestData[param.name] = parameterData.data; - } - } - - let resultPromise = this.functionRegistry_call(functionId, actionRequestData); - - resolve(resultPromise); - - }).catch( (err) => { - - reject(err); - }); - - }); - - return actionFunctionPromise; - } + @@ -546,190 +456,6 @@ class EducationPlatformApp { } - /** - * Converts a source value to a target type using the available conversion functions taking - * into consideration the related metamodel. - * - * TODO: To be moved to the ToolManager issue #40 - * - * @param {string} sourceValue - * @param {string} sourceType - * @param {string} metamodelValue - * @param {string} metamodelType - * @param {string} targetType - * @param {string} parameterName name of the parameter for request - * @returns {Promise} promise for the converted parameter value - */ - async convertIncludingMetamodel(sourceValue, sourceType, metamodelValue, metamodelType, targetType, parameterName){ - let parameterPromise; - let typesPanelValuesMap = {}; // Types have to be distinct for mapping to the conversion function's parameters - typesPanelValuesMap[sourceType]= sourceValue; - - let conversionFunctionId; - - let potentialConversionFunctions = this.functionRegistry_findPartial( [sourceType, ARRAY_ANY_ELEMENT], targetType); - - //check for a conversion function with the metamodel type - conversionFunctionId = await this.selectConversionFunctionConvertMetamodel( metamodelType, metamodelValue, potentialConversionFunctions, - false, parameterName, typesPanelValuesMap) - - if (conversionFunctionId==null){ - //no conversion found so check for a conversion function but consider conversions of the metamodel - conversionFunctionId = await this.selectConversionFunctionConvertMetamodel(metamodelType, metamodelValue, potentialConversionFunctions, - true, parameterName, typesPanelValuesMap); - } - - if (conversionFunctionId != null){ - //There is a matching conversion function - parameterPromise = this.functionRegistry_callConversion(conversionFunctionId, typesPanelValuesMap, parameterName); - - } else { - parameterPromise = null; - this.errorNotification("No conversion function available for input types:" + Object.keys(typesPanelValuesMap).toString() ) - } - - return parameterPromise; - } - - - /** - * For the given list of conversion function ids to check, finds the first conversion function with matching metamodel dependency. - * Optionally conversions of the metamodel are considered from the conversion functions available to the tools manager and - * the metamodel type. If available, the metamodel value is converted to the required type. - * - * @param {string} metamodelType the metamodel type - * @param {string} metamodelValue the metamodel value - * @param {string[]} conversionFunctions list of conversion function ids to check - * @param {boolean} convertMetamodel when true try to convert the metamodel using a remote tool service conversion function - * available to the ToolsManager. - * @param {string} parameterName the name of the parameter to use when converting the metamodel. - * @param {string[]} typeValueMap the type values map the metamodel input value is added to if a conversion function is found - * @returns {string} the id of a conversion function to use, null if none found. - */ - async selectConversionFunctionConvertMetamodel(metamodelType, metamodelValue, conversionFunctions, convertMetamodel, parameterName, typeValueMap){ - let conversionFunctionId = null; - let functionsToCheck = []; - - if (Array.isArray(conversionFunctions)){ - functionsToCheck = [...conversionFunctions]; - } - - while ( conversionFunctionId==null && functionsToCheck.length > 0){ - let functionId = functionsToCheck.pop(); - let conversionFunction = this.toolsManager.getActionFunction(functionId); - - // Lookup the conversion function's metamodel type - let metamodelName = conversionFunction.getInstanceOfParamName( conversionFunction.getParameters()[0].name ); - - if(metamodelName==null){ - metamodelName = conversionFunction.getInstanceOfParamName( conversionFunction.getParameters()[1].name ); - } - - const targetMetamodelType = conversionFunction.getParameterType(metamodelName); - - if (!convertMetamodel){ - // Check for conversion functions with matching metamodels only - - if (targetMetamodelType==metamodelType) { - //Conversion function found so use the panel value - - conversionFunctionId = functionId; - typeValueMap[metamodelType]= metamodelValue; - } - - } else { - // Check for conversion functions converting metamodel if possible - let metamodelConversionFunctionId = this.toolsManager.getConversionFunction( [metamodelType], targetMetamodelType ); - - if (metamodelConversionFunctionId != null){ - - conversionFunctionId = functionId; - - //convert metamodel - let metamodelTypeValueMap = {}; - metamodelTypeValueMap[metamodelType]=metamodelValue; // The found conversion function is expected to have one parameter - - let convertedValue = await this.functionRegistry_callConversion(metamodelConversionFunctionId, metamodelTypeValueMap, parameterName); - - typeValueMap[targetMetamodelType]= convertedValue.data; - } - } - } - - return conversionFunctionId; - } - - /** - * Prepares the input parameters and requests the type translation for the given function id - * - * TODO: To be moved to the FunctionRegistry issue #40 - * - * @param {string} functionId the id of the action function - * @param {Object} typeValuesMap an object mapping action functions parameter types as keys to input values - * @param {string} parameterName name of the parameter - * @returns Promise for the translated data - * - */ - functionRegistry_callConversion( functionId, typeValuesMap, parameterName ){ - let conversionRequestData = {}; - let conversionFunction = this.toolsManager.getActionFunction(functionId); - - // Populate parameters for the conversion request - for( const param of conversionFunction.getParameters() ){ - conversionRequestData[param.name] = typeValuesMap[param.type]; - } - - return this.requestTranslation(conversionRequestData, conversionFunction, parameterName); - } - - /** - * - * @param {string} functionId url of the function to call - * @param {Object} parameters object containing the parameters request data - * @returns - */ - functionRegistry_call(functionId, parameters ){ - - let actionFunction = this.toolsManager.getActionFunction(functionId); - let parametersJson = JSON.stringify(parameters); - - let requestPromise = jsonRequest(actionFunction.getPath(), parametersJson) - - return requestPromise; - } - - - /** - * Requests the conversion function from the remote tool service - * - * @param {Object} parameters - * @param {ActionFunction} conversionFunction - * @param {String} parameterName name of the parameter - * @returns Promise for the translated data - */ - requestTranslation(parameters, conversionFunction, parameterName){ - - let parametersJson = JSON.stringify(parameters); - - return jsonRequestConversion(conversionFunction.getPath(), parametersJson, parameterName); - } - - /** - * TODO: Temporary wrapper called function to be renamed and to be moved to the FunctionRegistry issue #40 - */ - functionRegistry_find(inputsParamTypes, outputParamType){ - return this.toolsManager.getConversionFunction( inputsParamTypes, outputParamType ); - } - - /** - * TODO: Temporary wrapper called function to be renamed and to be moved to the FunctionRegistry issue #40 - */ - functionRegistry_findPartial(inputsParamTypes, outputParamType){ - return this.toolsManager.getPartiallyMatchingConversionFunctions( inputsParamTypes, outputParamType ); - } - - - /** * Handle the response from the remote tool service * @@ -859,7 +585,6 @@ class EducationPlatformApp { //Buttons defined by tool buttonConfig= action.source.ref.buttons.find( btn => btn.id == sourceButton ); } - const toolActionFunction = this.toolsManager.getActionFunction( buttonConfig.actionfunction ); // TODO tidy up by resolving tool references // Create map containing panel values let parameterMap = new Map(); @@ -886,7 +611,7 @@ class EducationPlatformApp { // Add the platform language parameter let languageParam = {}; - languageParam.type = toolActionFunction.getParameterType("language"); + languageParam.type = ACTION_FUNCTION_LANGUAGE_TYPE; languageParam.value = action.source.ref.language; // Source panel language parameterMap.set("language", languageParam); @@ -895,7 +620,7 @@ class EducationPlatformApp { //actionRequestData.outputLanguage = outputLanguage; // Call backend conversion and service functions - let actionResultPromise = this.invokeActionFunction(buttonConfig.actionfunction, parameterMap); + let actionResultPromise = this.toolsManager.invokeActionFunction(buttonConfig.actionfunction, parameterMap); actionResultPromise.catch( () => { this.errorNotification("There was an error translating action function parameter types."); diff --git a/platform/src/FunctionRegistry.js b/platform/src/FunctionRegistry.js index b8867b4..6a3fdde 100644 --- a/platform/src/FunctionRegistry.js +++ b/platform/src/FunctionRegistry.js @@ -1,8 +1,16 @@ -import {arrayEquals} from "./Utility.js" +import {arrayEquals, jsonRequestConversion, jsonRequest} from "./Utility.js" class FunctionRegistry { register = []; +toolsManager; + + /** + * @param {ToolsManager} theToolsManager - the tools manager + */ + constructor(theToolsManager){ + this.toolsManager = theToolsManager; + } registerFunction( inputParameterTypes, outputParameterType, functionId ){ @@ -14,7 +22,7 @@ register = []; this.register.push( entry ); } - lookupFunction(inputParameterTypes, outputParameterType){ + find(inputParameterTypes, outputParameterType){ let foundEntry; @@ -33,7 +41,7 @@ register = []; } - lookupFunctionsPartialMatch(inputParameterTypes, outputParameterType){ + findPartial(inputParameterTypes, outputParameterType){ let foundEntries; @@ -51,6 +59,66 @@ register = []; } } + /** + * Resolves the id of an action function to the function itself + * + * @param {string} functionId + */ + resolve(functionId){ + return this.toolsManager.getActionFunction(functionId) + } + + /** + * Requests the conversion function from the remote tool service + * + * @param {Object} parameters + * @param {ActionFunction} conversionFunction + * @param {String} parameterName name of the parameter + * @returns Promise for the translated data + */ + requestTranslation(parameters, conversionFunction, parameterName){ + + let parametersJson = JSON.stringify(parameters); + + return jsonRequestConversion(conversionFunction.getPath(), parametersJson, parameterName); + } + + /** + * + * @param {string} functionId url of the function to call + * @param {Object} parameters object containing the parameters request data + * @returns + */ + call(functionId, parameters ){ + + let actionFunction = this.toolsManager.getActionFunction(functionId); + let parametersJson = JSON.stringify(parameters); + + let requestPromise = jsonRequest(actionFunction.getPath(), parametersJson) + + return requestPromise; + } + + /** + * Prepares the input parameters and requests the type translation for the given function id + * + * @param {string} functionId the id of the action function + * @param {Object} typeValuesMap an object mapping action functions parameter types as keys to input values + * @param {string} parameterName name of the parameter + * @returns Promise for the translated data + * + */ + callConversion( functionId, typeValuesMap, parameterName ){ + let conversionRequestData = {}; + let conversionFunction = this.toolsManager.getActionFunction(functionId); + + // Populate parameters for the conversion request + for( const param of conversionFunction.getParameters() ){ + conversionRequestData[param.name] = typeValuesMap[param.type]; + } + + return this.requestTranslation(conversionRequestData, conversionFunction, parameterName); + } } export { FunctionRegistry }; \ No newline at end of file diff --git a/platform/src/ToolsManager.js b/platform/src/ToolsManager.js index 0ec8792..6169269 100644 --- a/platform/src/ToolsManager.js +++ b/platform/src/ToolsManager.js @@ -1,21 +1,25 @@ -import { parseConfigFile } from "./Utility.js"; +import { parseConfigFile, ARRAY_ANY_ELEMENT } from "./Utility.js"; import { FunctionRegistry } from "../src/FunctionRegistry.js" import { ActionFunction } from "./ActionFunction.js"; import { ToolConfigValidator } from "./ToolConfigValidator.js"; import { EducationPlatformError } from "./EducationPlatformError.js"; class ToolManager { - toolId; toolsUrls; configErrors = []; configValidator; tools = {}; functionRegister; + errorNotification; - constructor(){ - this.configValidator= new ToolConfigValidator(); - this.functionRegister= new FunctionRegistry(); + /** + * @param {function(String)} errorNotifier - the function to call to display an error + */ + constructor(errorNotifier){ + this.configValidator = new ToolConfigValidator(); + this.functionRegister = new FunctionRegistry(this); + this.errorNotification = errorNotifier; } setToolsUrls(urls){ @@ -40,7 +44,6 @@ class ToolManager { } - /** * Fetches the tools from the tools url and populates tool's * functions and panel definitions. @@ -220,13 +223,11 @@ class ToolManager { } - - /** * Finds the action function for an action function Id * @param {*} actionFuntionId * - * @deprecated issue #40 + * @deprecated issue #192 */ getActionFunction(actionFuntionId) { for ( const toolskey of Object.keys(this.tools)){ @@ -240,19 +241,6 @@ class ToolManager { console.log("Tool with function id '" + actionFuntionId + "' was not found."); return null; - - } - - - /** - * Resolves the id of an action function to the function itself - * - * TODO: To be moved to the FunctionRegistry issue #40 - * - * @param {string} functionId - */ - functionRegistry_resolve(functionId){ - return this.getActionFunction(functionId) } @@ -310,28 +298,249 @@ class ToolManager { } + /** + * Returns the errors found parsing and validating the tool configuration files + * @returns array of errors + */ + getConfigErrors(){ + return this.configErrors; + } + + /** - * Returns the id of the registered function with matching input and output parameters. - * @param {*} inputsParamTypes - * @param {*} outputParamType - * @returns The id of a matching function + * Invokes an action function by placing requests to all the required external tool functions + * + * @param {string} functionId the id of tool function + * @param {Map} parameterMap map from parameter name to its value and type + * @returns {Promise} promise to result of the action function */ - getConversionFunction(inputsParamTypes, outputParamType){ + invokeActionFunction(functionId, parameterMap){ + + let actionFunction = this.functionRegister.resolve(functionId); + + let parameterPromises = []; + + for ( const paramName of parameterMap.keys() ){ /* TODO add defensive checks that every required value + is provided issue #57 */ + + let actionFunctionParam = actionFunction.getParameters().find( p => p.name === paramName); + + /* Check the given parameter types against the those of the requested action function. + If required, request conversion from available tool functions */ + let givenParameter = parameterMap.get(paramName); + + if (givenParameter.type != actionFunctionParam.type){ + //Types don't match so try and convert + let convertedValue; + + const metamodelId = actionFunction.getInstanceOfParamName(paramName); + + if(metamodelId==null){ + // Convert with no metamodel to consider + convertedValue = this.convert( givenParameter.value, givenParameter.type, + actionFunctionParam.type, paramName ); // TODO issue #58 remove paramName + + } else { + // Convert considering metamodel + const givenMetamodel = parameterMap.get(metamodelId); + + convertedValue = this.convertIncludingMetamodel( givenParameter.value , givenParameter.type, + givenMetamodel.value, givenMetamodel.type, + actionFunctionParam.type, paramName ); // TODO issue #58 remove paramName + } + + parameterPromises.push(convertedValue); + + } else { + // Matching types add values to promise for synchronisation + let value = new Promise( function (resolve) { + let parameterData = {}; + + parameterData.name = paramName; + parameterData.data = givenParameter.value; - return this.functionRegister.lookupFunction(inputsParamTypes, outputParamType); + resolve(parameterData); + }); + + parameterPromises.push(value); + } + } + + // Invoke the actionFunction on completion of any conversions + let actionFunctionPromise = new Promise((resolve, reject) => { + + Promise.all( parameterPromises ).then( (values) => { + let actionRequestData = {}; + + //Populate the transformed parameters + for ( const param of actionFunction.getParameters() ){ + + const panelConfig = parameterMap.get(param.name); + + if (panelConfig != undefined){ + let parameterData = values.find(val => (val.name === param.name) ); + + actionRequestData[param.name] = parameterData.data; + } + } + + let resultPromise = this.functionRegister.call(functionId, actionRequestData); + + resolve(resultPromise); + + }).catch( (err) => { + + reject(err); + }); + + }); + + return actionFunctionPromise; } - getPartiallyMatchingConversionFunctions(inputsParamTypes, outputParamType){ + /** + * Converts a source value to a target type using the available conversion functions + * + * @param {string} sourceValue + * @param {string} sourceType + * @param {string} targetType + * @param {string} parameterName name of the parameter for request + * @returns {Promise} promise for the converted parameter value + */ + convert(sourceValue, sourceType, targetType, parameterName){ - return this.functionRegister.lookupFunctionsPartialMatch(inputsParamTypes, outputParamType); - } + let parameterPromise; + let typesPanelValuesMap = {}; // Types have to be distinct for mapping to the conversion function's paramters + typesPanelValuesMap[sourceType]= sourceValue; - /** - * Returns the errors found parsing and validating the tool configuration files - * @returns array of errors + let conversionFunctionId = this.functionRegister.find( Object.keys(typesPanelValuesMap), targetType ); + + if (conversionFunctionId != null){ + //There is a matching conversion function + parameterPromise = this.functionRegister.callConversion(conversionFunctionId, typesPanelValuesMap, parameterName); + + } else { + parameterPromise = null; + + this.errorNotification("No conversion function available for input types:" + Object.keys(typesPanelValuesMap).toString() ) + } + + return parameterPromise; + } + + + /** + * Converts a source value to a target type using the available conversion functions taking + * into consideration the related metamodel. + * + * TODO: To be moved to the ToolManager issue #40 + * + * @param {string} sourceValue + * @param {string} sourceType + * @param {string} metamodelValue + * @param {string} metamodelType + * @param {string} targetType + * @param {string} parameterName name of the parameter for request + * @returns {Promise} promise for the converted parameter value */ - getConfigErrors(){ - return this.configErrors; + async convertIncludingMetamodel(sourceValue, sourceType, metamodelValue, metamodelType, targetType, parameterName){ + let parameterPromise; + let typesPanelValuesMap = {}; // Types have to be distinct for mapping to the conversion function's parameters + typesPanelValuesMap[sourceType]= sourceValue; + + let conversionFunctionId; + + let potentialConversionFunctions = this.functionRegister.findPartial( [sourceType, ARRAY_ANY_ELEMENT], targetType); + + //check for a conversion function with the metamodel type + conversionFunctionId = await this.selectConversionFunctionConvertMetamodel( metamodelType, metamodelValue, potentialConversionFunctions, + false, parameterName, typesPanelValuesMap) + + if (conversionFunctionId==null){ + //no conversion found so check for a conversion function but consider conversions of the metamodel + conversionFunctionId = await this.selectConversionFunctionConvertMetamodel(metamodelType, metamodelValue, potentialConversionFunctions, + true, parameterName, typesPanelValuesMap); + } + + if (conversionFunctionId != null){ + //There is a matching conversion function + parameterPromise = this.functionRegister.callConversion(conversionFunctionId, typesPanelValuesMap, parameterName); + + } else { + parameterPromise = null; + + this.errorNotification("No conversion function available for input types:" + Object.keys(typesPanelValuesMap).toString() ) + } + + return parameterPromise; + } + + + /** + * For the given list of conversion function ids to check, finds the first conversion function with matching metamodel dependency. + * Optionally conversions of the metamodel are considered from the conversion functions available to the tools manager and + * the metamodel type. If available, the metamodel value is converted to the required type. + * + * @param {string} metamodelType the metamodel type + * @param {string} metamodelValue the metamodel value + * @param {string[]} conversionFunctions list of conversion function ids to check + * @param {boolean} convertMetamodel when true try to convert the metamodel using a remote tool service conversion function + * available to the ToolsManager. + * @param {string} parameterName the name of the parameter to use when converting the metamodel. + * @param {string[]} typeValueMap the type values map the metamodel input value is added to if a conversion function is found + * @returns {string} the id of a conversion function to use, null if none found. + */ + async selectConversionFunctionConvertMetamodel(metamodelType, metamodelValue, conversionFunctions, convertMetamodel, parameterName, typeValueMap){ + let conversionFunctionId = null; + let functionsToCheck = []; + + if (Array.isArray(conversionFunctions)){ + functionsToCheck = [...conversionFunctions]; + } + + while ( conversionFunctionId==null && functionsToCheck.length > 0){ + let functionId = functionsToCheck.pop(); + let conversionFunction = this.getActionFunction(functionId); + + // Lookup the conversion function's metamodel type + let metamodelName = conversionFunction.getInstanceOfParamName( conversionFunction.getParameters()[0].name ); + + if(metamodelName==null){ + metamodelName = conversionFunction.getInstanceOfParamName( conversionFunction.getParameters()[1].name ); + } + + const targetMetamodelType = conversionFunction.getParameterType(metamodelName); + + if (!convertMetamodel){ + // Check for conversion functions with matching metamodels only + + if (targetMetamodelType==metamodelType) { + //Conversion function found so use the panel value + + conversionFunctionId = functionId; + typeValueMap[metamodelType]= metamodelValue; + } + + } else { + // Check for conversion functions converting metamodel if possible + let metamodelConversionFunctionId = this.functionRegister.find( [metamodelType], targetMetamodelType ); + + if (metamodelConversionFunctionId != null){ + + conversionFunctionId = functionId; + + //convert metamodel + let metamodelTypeValueMap = {}; + metamodelTypeValueMap[metamodelType]=metamodelValue; // The found conversion function is expected to have one parameter + + let convertedValue = await this.functionRegister.callConversion(metamodelConversionFunctionId, metamodelTypeValueMap, parameterName); + + typeValueMap[targetMetamodelType]= convertedValue.data; + } + } + } + + return conversionFunctionId; } } diff --git a/platform/test/spec/testEducationPlatformAppSpec.js b/platform/test/spec/testEducationPlatformAppSpec.js index 8a7922e..df37d92 100644 --- a/platform/test/spec/testEducationPlatformAppSpec.js +++ b/platform/test/spec/testEducationPlatformAppSpec.js @@ -1,4 +1,4 @@ -/*global describe, it, expect, spyOn, beforeEach, afterEach, expectAsync -- functions provided by Jasmine */ +/*global describe, it, expect, spyOn, beforeEach, afterEach -- functions provided by Jasmine */ /*global jasmine -- object provided by Jasmine */ /*global $ -- jquery is externally imported*/ @@ -58,20 +58,20 @@ describe("EducationPlatformApp", () => { platform.activityManager = activityManagerSpy; // tools manager - let toolsManagerSpy = jasmine.createSpyObj(['getActionFunction']); + let toolsManagerSpy = jasmine.createSpyObj(['getActionFunction', 'invokeActionFunction']); toolsManagerSpy.getActionFunction.and.returnValue(new ActionFunction({ parameters: [ {name: "language", type: "text"} ] })); - platform.toolsManager = toolsManagerSpy; - - // platform - invoke action function + invokeReturnedPromise = new Promise(function(resolve) { resolve(true); }) - spyInvokeActionFunction = spyOn(EducationPlatformApp.prototype, "invokeActionFunction").and.returnValue(invokeReturnedPromise); - + spyInvokeActionFunction = toolsManagerSpy.invokeActionFunction.and.returnValue(invokeReturnedPromise); + + platform.toolsManager = toolsManagerSpy; + // platform - handle response spyOn(EducationPlatformApp.prototype, "handleResponseActionFunction"); @@ -87,7 +87,7 @@ describe("EducationPlatformApp", () => { platform.runAction(PANEL_ID, BUTTON_ID); // Check the expected results - expect(platform.invokeActionFunction).toHaveBeenCalledWith( + expect(platform.toolsManager.invokeActionFunction).toHaveBeenCalledWith( jasmine.anything(), jasmine.mapContaining( new Map([expectedLanguageEntry]) ) ); }) @@ -102,7 +102,7 @@ describe("EducationPlatformApp", () => { platform.runAction(PANEL_ID, BUTTON_ID); // Check the expected results - expect(platform.invokeActionFunction).toHaveBeenCalledWith(ACTION_FUNCTION_ID, expectedParamMap); + expect(platform.toolsManager.invokeActionFunction).toHaveBeenCalledWith(ACTION_FUNCTION_ID, expectedParamMap); }) it("calls handleResponseActionFunction with the current action and the invoked function's result promise", () => { @@ -137,595 +137,6 @@ describe("EducationPlatformApp", () => { }) - describe( "invokeActionFunction()", ()=>{ - const ACTION_FUNCTION_ID = "function-test"; - const TOOL_LANGUAGE = "lang"; - - const PARAM1_NAME = "param1"; - const PARAM1_VALUE = "panel-1's contents"; - const PARAM1_CONVERTED_VALUE = "param1's converted contents"; - - const PARAM2_NAME = "param2"; - const PARAM2_VALUE = "param2's contents"; - const PARAM2_CONVERTED_VALUE = "param2's converted contents"; - - // types the test action functions are expecting - const ACTION_FUNCTION_PARAM1_TYPE = "type1"; - const ACTION_FUNCTION_PARAM2_TYPE = "type2"; - const ACTION_FUNCTION_RESULT= "Test function result"; - - let platform; - let toolsManagerSpy; - - beforeEach(() => { - // Setup - platform = new EducationPlatformApp(); - - // platform - toolsmanager - toolsManagerSpy = jasmine.createSpyObj(['functionRegistry_resolve']); - - toolsManagerSpy.functionRegistry_resolve.and.returnValue( - new ActionFunction({ - parameters: [ - {name: PARAM1_NAME, type: ACTION_FUNCTION_PARAM1_TYPE}, - {name: "language", type: "text"} - ] - }) - ); - - platform.toolsManager = toolsManagerSpy; - - // platform - functionRegistry_call - spyOn (EducationPlatformApp.prototype, "functionRegistry_call").and.returnValue ( - new Promise(function(resolve) { - resolve(ACTION_FUNCTION_RESULT); - }) - ); - }) - - it("returns the result via a promise", async () => { - const PARAM1_TYPE = ACTION_FUNCTION_PARAM1_TYPE; - - const parameterMap = new Map ( - [[PARAM1_NAME, {type: PARAM1_TYPE, value: PARAM1_VALUE}], - ["language", {type: "text", value: TOOL_LANGUAGE }] ] - ) - - // Call the target object - const returnedResult = platform.invokeActionFunction(ACTION_FUNCTION_ID, parameterMap); - - await expectAsync(returnedResult).toBeResolvedTo(ACTION_FUNCTION_RESULT); - }) - - it("calls functionRegistry_call with the given parameter values for matching types", async () => { - const PARAM1_TYPE = ACTION_FUNCTION_PARAM1_TYPE; - - const parameterMap = new Map ( - [[PARAM1_NAME, {type: PARAM1_TYPE, value: PARAM1_VALUE}], - ["language", {type: "text", value: TOOL_LANGUAGE }] ] - ) - - // Call the target object - await platform.invokeActionFunction(ACTION_FUNCTION_ID, parameterMap); - - // Check the expected results - const EXPECTED_PARAM_VALUES = { - [PARAM1_NAME]: PARAM1_VALUE, - "language": TOOL_LANGUAGE - } - - expect(platform.functionRegistry_call).toHaveBeenCalledWith(ACTION_FUNCTION_ID, EXPECTED_PARAM_VALUES); - }) - - it("calls functionRegistry_call with the converted parameter values for non-matching types", async () => { - const PARAM1_TYPE = "typex"; - - const parameterMap = new Map ( - [[PARAM1_NAME, {type: PARAM1_TYPE, value: PARAM1_VALUE}], - ["language", {type: "text", value: TOOL_LANGUAGE }] ] - ) - - spyOn( EducationPlatformApp.prototype, "convert").and.returnValue( - new Promise(function(resolve) { - resolve( {name: PARAM1_NAME, data: PARAM1_CONVERTED_VALUE} ); - }) - ); - - // Call the target object - await platform.invokeActionFunction(ACTION_FUNCTION_ID, parameterMap); - - // Check the expected results - const EXPECTED_PARAM_VALUES = { - [PARAM1_NAME]: PARAM1_CONVERTED_VALUE, - "language": TOOL_LANGUAGE - } - - expect(platform.convert).toHaveBeenCalledWith(PARAM1_VALUE, PARAM1_TYPE, ACTION_FUNCTION_PARAM1_TYPE, PARAM1_NAME); - expect(platform.functionRegistry_call).toHaveBeenCalledWith(ACTION_FUNCTION_ID, EXPECTED_PARAM_VALUES); - }) - - it("calls functionRegistry_call with the converted parameter values for non-matching types including a metamodel", async () => { - const PARAM1_TYPE = "typex"; - const PARAM2_TYPE = "typez"; - - const parameterMap = new Map ( // input to invokeActionFunction - [[PARAM1_NAME, {type: PARAM1_TYPE, value: PARAM1_VALUE}], - [PARAM2_NAME, {type: PARAM2_TYPE, value: PARAM2_VALUE}], - ["language", {type: "text", value: TOOL_LANGUAGE }] ] - ) - - // platform - toolsManager - toolsManagerSpy.functionRegistry_resolve.and.returnValue( - new ActionFunction({ - parameters: [ - {name: PARAM1_NAME, type: ACTION_FUNCTION_PARAM1_TYPE, instanceOf: PARAM2_NAME}, - {name: PARAM2_NAME, type: ACTION_FUNCTION_PARAM2_TYPE}, - {name: "language", type: "text"} - ] - }) - ); - - // platform - conversion function spies - spyOn( EducationPlatformApp.prototype, "convertIncludingMetamodel").and.returnValue( - new Promise(function(resolve) { - resolve( {name: PARAM1_NAME, data: PARAM1_CONVERTED_VALUE} ); - }) - ); - - spyOn( EducationPlatformApp.prototype, "convert").and.returnValue( - new Promise(function(resolve) { - resolve( {name: PARAM2_NAME, data: PARAM2_CONVERTED_VALUE} ); - }) - ); - - // Call the target object - await platform.invokeActionFunction(ACTION_FUNCTION_ID, parameterMap); - - // Check the expected results - const EXPECTED_PARAM_VALUES = { - [PARAM1_NAME]: PARAM1_CONVERTED_VALUE, - [PARAM2_NAME]: PARAM2_CONVERTED_VALUE, - "language": TOOL_LANGUAGE - } - - expect(platform.convert).toHaveBeenCalledWith(PARAM2_VALUE, PARAM2_TYPE, ACTION_FUNCTION_PARAM2_TYPE, PARAM2_NAME); - - expect(platform.convertIncludingMetamodel).toHaveBeenCalledWith(PARAM1_VALUE, PARAM1_TYPE, - PARAM2_VALUE, PARAM2_TYPE, - ACTION_FUNCTION_PARAM1_TYPE, PARAM1_NAME); - - expect(platform.functionRegistry_call).toHaveBeenCalledWith(ACTION_FUNCTION_ID, EXPECTED_PARAM_VALUES); - }) - - it("sends requests to a conversion function's url without unused parameters", async () => { - const PARAM1_TYPE = ACTION_FUNCTION_PARAM1_TYPE; - - const parameterMap = new Map ( - [[PARAM1_NAME, {type: PARAM1_TYPE, value: PARAM1_VALUE}], - ["language", {type: "text", value: TOOL_LANGUAGE }] ] - ) - - // platform - toolsManager - toolsManagerSpy.functionRegistry_resolve.and.returnValue( - new ActionFunction({ - parameters: [ - {name: PARAM1_NAME, type: ACTION_FUNCTION_PARAM1_TYPE, instanceOf: PARAM2_NAME}, - {name: PARAM2_NAME, type: ACTION_FUNCTION_PARAM2_TYPE}, - {name: "language", type: "text"} - ] - }) - ); - - // Call the target object - await platform.invokeActionFunction(ACTION_FUNCTION_ID, parameterMap); - - // Check the expected results - const EXPECTED_PARAM_VALUES = { - [PARAM1_NAME]: PARAM1_VALUE, - "language": TOOL_LANGUAGE - } - - expect(platform.functionRegistry_call).toHaveBeenCalledWith(ACTION_FUNCTION_ID, EXPECTED_PARAM_VALUES); - }) - - }) - - describe("convert()", () => { - let platform; - let findConversionSpy; - - const FILE_CONTENTS = "Test file contents."; - const SOURCE_TYPE = "test-source-type"; - const TARGET_TYPE = "test-target-type"; - const PARAM_NAME = "test"; - const callConversionReturn = new Promise(function(resolve) { - resolve(true); - }) - - const CONVERSION_FUNCTION_ID = "conversion-function-id"; - - beforeEach(()=>{ - // Setup - findConversionSpy = spyOn( EducationPlatformApp.prototype, "functionRegistry_find"). - and.returnValue(CONVERSION_FUNCTION_ID); - - spyOn( EducationPlatformApp.prototype, "functionRegistry_callConversion").and.returnValue( - callConversionReturn); - - spyOn( EducationPlatformApp.prototype, "errorNotification"); - - platform = new EducationPlatformApp(); - - }) - - it("calls functionRegistry_callConversion on a conversion function being available", ()=>{ - // Call the target object - platform.convert(FILE_CONTENTS, SOURCE_TYPE, TARGET_TYPE, PARAM_NAME); - - // Check the expected results - expect(platform.functionRegistry_callConversion).toHaveBeenCalledWith( - CONVERSION_FUNCTION_ID, { [SOURCE_TYPE]: FILE_CONTENTS } , PARAM_NAME - ); - - expect(platform.errorNotification).not.toHaveBeenCalled(); - }) - - it("returns a promise on a conversion function being available", ()=> { - // Call the target object - const convertResult = platform.convert(FILE_CONTENTS, SOURCE_TYPE, TARGET_TYPE, PARAM_NAME); - - // Check the expected results - expect(convertResult).toEqual(callConversionReturn); - }) - - it("returns null and provides an error notification on a conversion function not being available", ()=> { - findConversionSpy.and.returnValue(null); - - // Call the target object - const convertResult = platform.convert(FILE_CONTENTS, SOURCE_TYPE, TARGET_TYPE, PARAM_NAME); - - // Check the expected results - expect(convertResult).toEqual(null); - expect(platform.errorNotification).toHaveBeenCalledWith(jasmine.stringMatching("(N|n)o conversion function")) - }) - - }) - - describe("convertIncludingMetamodel()", () => { - let platform; - let findConversionSpy; - - const FILE_CONTENTS = "Test file contents."; - const SOURCE_TYPE = "test-source-type"; - const TARGET_TYPE = "test-target-type"; - const MM_FILE_CONTENTS = "Test metamodel file contents." - const MM_TYPE = "test-metamodel-type"; - const PARAM_NAME = "test"; - const callConversionReturn = new Promise(function(resolve) { - resolve(true); - }) - - const CONVERSION_FUNCTION_ID = "conversion-function-id"; - - beforeEach(()=>{ - // Setup - findConversionSpy = spyOn( EducationPlatformApp.prototype, "functionRegistry_findPartial"). - and.returnValue([CONVERSION_FUNCTION_ID]); - - spyOn( EducationPlatformApp.prototype, "functionRegistry_callConversion").and.returnValue( - callConversionReturn); - - spyOn( EducationPlatformApp.prototype, "errorNotification"); - - platform = new EducationPlatformApp(); - - // platform - toolsManager - let toolsManagerSpy = jasmine.createSpyObj(['getActionFunction']); - toolsManagerSpy.getActionFunction.and.returnValue(new ActionFunction({ - parameters: [ - {name: "input", type: SOURCE_TYPE, instanceOf: "metamodel"}, - {name: "metamodel", type: MM_TYPE} - ] - })); - platform.toolsManager= toolsManagerSpy; - - }) - - it("calls functionRegistry_callConversion on a conversion function being available", async ()=> { - // Call the target object - await platform.convertIncludingMetamodel(FILE_CONTENTS, SOURCE_TYPE, MM_FILE_CONTENTS, MM_TYPE, TARGET_TYPE, PARAM_NAME); - - // Check the expected results - expect(platform.functionRegistry_callConversion).toHaveBeenCalledWith( - CONVERSION_FUNCTION_ID, {[SOURCE_TYPE]: FILE_CONTENTS, [MM_TYPE]: MM_FILE_CONTENTS } , PARAM_NAME - ); - - expect(platform.errorNotification).not.toHaveBeenCalled(); - }) - - it("returns a promise on a conversion function being available", async () => { - // Call the target object - const convertResult = platform.convertIncludingMetamodel(FILE_CONTENTS, SOURCE_TYPE, MM_FILE_CONTENTS, MM_TYPE, TARGET_TYPE, PARAM_NAME); - - // Check the expected results - await expectAsync(convertResult).toBePending(); - }) - - it("returns null and provides an error notification on a conversion function not being available", async () => { - findConversionSpy.and.returnValue(null); - - // Call the target object - const convertResult = await platform.convertIncludingMetamodel(FILE_CONTENTS, SOURCE_TYPE, MM_FILE_CONTENTS, MM_TYPE, TARGET_TYPE, PARAM_NAME); - - // Check the expected results - expect(convertResult).toEqual(null); - expect(platform.errorNotification).toHaveBeenCalledWith(jasmine.stringMatching("(N|n)o conversion function")) - }) - - }) - - describe("selectConversionFunctionConvertMetamodel()", () => { - let platform; - - const SOURCE_TYPE = "test-source-type"; - const FILE_CONTENTS = "Test file contents."; - const MM_FILE_CONTENTS = "Test metamodel file contents." - const MM_TARGET_TYPE = "test-metamodel-target-type"; - const PARAM_NAME = "test"; - - const CONVERSION_FUNCTION_ID = "conversion-function-id"; - const X_FUNCTION_ID = "x-function-id"; // Not interested - - let toolsManagerSpy; - - beforeEach( () => { - // Setup - spyOn( EducationPlatformApp.prototype, "errorNotification"); - - platform = new EducationPlatformApp(); - - // platform - toolsManager - toolsManagerSpy = jasmine.createSpyObj(['getActionFunction', 'getConversionFunction']); - - toolsManagerSpy.getActionFunction.and.callFake( (functionId) => { - let actionFunctionConfig; - - switch (functionId){ - case CONVERSION_FUNCTION_ID: - actionFunctionConfig = { - parameters: [ - {name: "input", type: SOURCE_TYPE, instanceOf: "metamodel"}, - {name: "metamodel", type: MM_TARGET_TYPE} - ] - }; - break; - - case X_FUNCTION_ID: - actionFunctionConfig = { - parameters: [ - {name: "input", type: SOURCE_TYPE, instanceOf: "metamodel"}, - {name: "metamodel", type: "X"} - ] - } - break; - default: - actionFunctionConfig = null; - } - - return new ActionFunction(actionFunctionConfig) - }) - platform.toolsManager = toolsManagerSpy; - - }) - - it("returns a function id if a conversion is possible without considering the metamodel", async () => { - const CONSIDER_MM = false; - let mm_type = MM_TARGET_TYPE; - const typeValueMap = { [SOURCE_TYPE]: FILE_CONTENTS } - - // Call the target object - let selectConversionResult = await platform.selectConversionFunctionConvertMetamodel(mm_type, MM_FILE_CONTENTS, [CONVERSION_FUNCTION_ID, X_FUNCTION_ID], CONSIDER_MM, PARAM_NAME, typeValueMap) - - // Check the expected results - expect(selectConversionResult).toEqual(CONVERSION_FUNCTION_ID); - }) - - it("returns null if a conversion is not possible without considering the metamodel", async () => { - const CONSIDER_MM = false; - let mm_type = MM_TARGET_TYPE; - const typeValueMap = { [SOURCE_TYPE]: FILE_CONTENTS } - - // Call the target object - let selectConversionResult = await platform.selectConversionFunctionConvertMetamodel(mm_type, MM_FILE_CONTENTS, [X_FUNCTION_ID, X_FUNCTION_ID], CONSIDER_MM, PARAM_NAME, typeValueMap) - - // Check the expected results - expect(selectConversionResult).toEqual(null); - }) - - it("returns a function id, converts the metamodel, and adds the converted metamodel value to the typeValueMap if a conversion is possible considering the metamodel", async ()=>{ - const CONSIDER_MM = true; - let mm_type = "test-metamodel-type"; - const typeValueMap = { [SOURCE_TYPE]: FILE_CONTENTS } - - const MM_CONVERSION_FUNCTION_ID = "metamodel-conversion-function-id"; - const MM_CONVERTED_CONTENTS = "Test converted metamodel contents."; - - const callConversionReturn = new Promise(function(resolve) { - resolve({data: MM_CONVERTED_CONTENTS}); - }) - spyOn( EducationPlatformApp.prototype, "functionRegistry_callConversion").and.returnValue( - callConversionReturn); - - toolsManagerSpy.getConversionFunction.and.returnValues(null, MM_CONVERSION_FUNCTION_ID); // Find possible conversion on the second call - - // Call the target object - let selectConversionResult = await platform.selectConversionFunctionConvertMetamodel(mm_type, MM_FILE_CONTENTS, [CONVERSION_FUNCTION_ID, X_FUNCTION_ID], CONSIDER_MM, PARAM_NAME, typeValueMap) - - // Check the expected results - expect(platform.functionRegistry_callConversion).toHaveBeenCalledWith( - MM_CONVERSION_FUNCTION_ID, { [mm_type]: MM_FILE_CONTENTS}, PARAM_NAME - ); - - expect(typeValueMap[MM_TARGET_TYPE]).toEqual(MM_CONVERTED_CONTENTS); - - expect(selectConversionResult).toEqual(CONVERSION_FUNCTION_ID); - }) - - it("returns null if no conversion is available considering the metamodel", async () => { - const CONSIDER_MM = true; - let mm_type = "test-metamodel-type"; - const typeValueMap = { [SOURCE_TYPE]: FILE_CONTENTS } - - spyOn( EducationPlatformApp.prototype, "functionRegistry_callConversion"); - - toolsManagerSpy.getConversionFunction.and.returnValues(null, null); // Do not find possible conversion - - // Call the target object - let selectConversionResult = await platform.selectConversionFunctionConvertMetamodel(mm_type, MM_FILE_CONTENTS, [CONVERSION_FUNCTION_ID, X_FUNCTION_ID], CONSIDER_MM, PARAM_NAME, typeValueMap) - - // Check the expected results - expect(platform.functionRegistry_callConversion).not.toHaveBeenCalled(); - - expect(typeValueMap[MM_TARGET_TYPE]).toEqual(undefined); - - expect(selectConversionResult).toEqual(null); - }) - }) - - describe("functionRegistry_call()", () => { - const TOOL_URL = "test://t1.url/toolfunction"; - const TOOL_RESPONSE = '{ "validationResult": "PASS", "output": "Test" }'; - const CONVERSION_FUNCTION_ID = "test-function-id"; - - const PARAMETER_1_NAME = "test-param-1"; - const PARAMETER_2_NAME = "language"; - - const PARAMETERS_INPUT = { - [PARAMETER_1_NAME]: "Parameter 1 value", - [PARAMETER_2_NAME]: "Parameter 2 value" - }; - - let platform; - - - beforeEach(()=>{ - // Setup - jasmine.Ajax.install(); - - platform = new EducationPlatformApp(); - - jasmine.Ajax.stubRequest(TOOL_URL).andReturn({ - "responseText": TOOL_RESPONSE, - "status": 200 - }); - - // platform - toolsManager - let toolsManagerSpy = jasmine.createSpyObj(['getActionFunction']); - toolsManagerSpy.getActionFunction.and.returnValue(new ActionFunction({ - path: TOOL_URL - })); - platform.toolsManager= toolsManagerSpy; - - }) - - afterEach(function() { - jasmine.Ajax.uninstall(); - }); - - it("returns the result via promise", async () => { - // Call the target object - const functionResponse = platform.functionRegistry_call(CONVERSION_FUNCTION_ID, PARAMETERS_INPUT); - - // Check the expected results - await expectAsync(functionResponse).toBeResolvedTo(TOOL_RESPONSE); - }) - - it("sends a request to the tool service url", async () => { - // Call the target object - platform.functionRegistry_call(CONVERSION_FUNCTION_ID, PARAMETERS_INPUT); - - // Check the expected results - const request = jasmine.Ajax.requests.mostRecent(); - expect(request.data()).toEqual( PARAMETERS_INPUT ); - }) - }) - - describe("functionRegistry_callConversion()", () => { - const TOOL_URL = "test://t1.url/toolfunction"; - const CONVERSION_FUNCTION_ID = "test-function-id"; - const PARAMETER_NAME = "testParameter1"; - const TYPE_MODEL = "type-model"; - const TYPE_METAMODEL = "type-metamodel"; - const MODEL_CONTENTS = "Parameter 1 model value"; - const METAMODEL_CONTENTS = "Parameter 2 metamodel value"; - - const TYPE_MAP_INPUT = { - [TYPE_MODEL]: MODEL_CONTENTS, - [TYPE_METAMODEL]: METAMODEL_CONTENTS - }; - - const CONVERSION_PARAM_IN = "input"; - const CONVERSION_PARAM_MM = "metamodel"; - const CONVERTED_MODEL = "Converted model contents."; - const TOOL_RESPONSE = `{"output": "${CONVERTED_MODEL}"}`; - - let platform; - - - beforeEach(()=>{ - // Setup - jasmine.Ajax.install(); - - platform = new EducationPlatformApp(); - - // xhr - jasmine.Ajax.stubRequest(TOOL_URL).andReturn({ - "responseText": TOOL_RESPONSE, - "status": 200 - }); - - // platform - toolsManager - let toolsManagerSpy = jasmine.createSpyObj(['getActionFunction']); - toolsManagerSpy.getActionFunction.and.returnValue(new ActionFunction({ - parameters: [ - {name: CONVERSION_PARAM_IN, type: TYPE_MODEL, instanceOf: "metamodel"}, - {name: CONVERSION_PARAM_MM, type: TYPE_METAMODEL} - ], - path: TOOL_URL - })); - platform.toolsManager= toolsManagerSpy; - }) - - afterEach(function() { - jasmine.Ajax.uninstall(); - }); - - it("sends a request to the tool service url", async () => { - const EXPECTED_REQUEST = { - [CONVERSION_PARAM_IN]: MODEL_CONTENTS, - [CONVERSION_PARAM_MM]: METAMODEL_CONTENTS - } - - // Call the target object - platform.functionRegistry_callConversion(CONVERSION_FUNCTION_ID, TYPE_MAP_INPUT, PARAMETER_NAME); - - // Check the expected results - const request = jasmine.Ajax.requests.mostRecent(); - expect(request.data()).toEqual( EXPECTED_REQUEST ); - }) - - it("returns the converted result via a promise", async () => { - const EXPECTED_RESPONSE = { name: PARAMETER_NAME, data: CONVERTED_MODEL }; // Format given by utility jsonRequestConversion() - - // Call the target object - const conversionResponse = platform.functionRegistry_callConversion(CONVERSION_FUNCTION_ID, TYPE_MAP_INPUT, PARAMETER_NAME); - - // Check the expected results - await expectAsync(conversionResponse).toBeResolvedTo(EXPECTED_RESPONSE); - }) - }) - - describe("notification()", () => { let platform; diff --git a/platform/test/spec/testFunctionRegistrySpec.js b/platform/test/spec/testFunctionRegistrySpec.js index ef71460..9f677c6 100644 --- a/platform/test/spec/testFunctionRegistrySpec.js +++ b/platform/test/spec/testFunctionRegistrySpec.js @@ -1,15 +1,19 @@ +/*global describe, it, expect, beforeEach, afterEach, expectAsync -- functions provided by Jasmine */ +/*global jasmine -- object provided by Jasmine */ + import {FunctionRegistry} from "../../src/FunctionRegistry.js" +import { ActionFunction } from "../../src/ActionFunction.js"; describe("FunctionRegistry", () => { - it("a registered function can be looked up", () => { + it("can find a registered function", () => { let registry = new FunctionRegistry(); registry.registerFunction(["A","B","C"], "D", "fn1" ); registry.registerFunction(["E","F","G"], "H", "fn2"); - let found = registry.lookupFunction(["E","F","G"], "H"); + let found = registry.find(["E","F","G"], "H"); expect(found).toBe("fn2"); @@ -24,7 +28,7 @@ describe("FunctionRegistry", () => { registry.registerFunction(["C","B"], "O", "fn2"); registry.registerFunction(["D","E"], "P", "fn3"); - let found = registry.lookupFunctionsPartialMatch(["*","B"], "O"); + let found = registry.findPartial(["*","B"], "O"); expect(found).toEqual(["fn1","fn2"]); @@ -38,11 +42,140 @@ describe("FunctionRegistry", () => { registry.registerFunction(["C","B"], "O", "fn2"); registry.registerFunction(["D","E"], "P", "fn3"); - let found = registry.lookupFunctionsPartialMatch(["*","B"], "O"); + let found = registry.findPartial(["*","B"], "O"); expect(found).toEqual(["fn2"]); }) + describe("call()", () => { + const TOOL_URL = "test://t1.url/toolfunction"; + const TOOL_RESPONSE = '{ "validationResult": "PASS", "output": "Test" }'; + const CONVERSION_FUNCTION_ID = "test-function-id"; + + const PARAMETER_1_NAME = "test-param-1"; + const PARAMETER_2_NAME = "language"; + + const PARAMETERS_INPUT = { + [PARAMETER_1_NAME]: "Parameter 1 value", + [PARAMETER_2_NAME]: "Parameter 2 value" + }; + + let registry; + + + beforeEach(()=>{ + // Setup + jasmine.Ajax.install(); + + jasmine.Ajax.stubRequest(TOOL_URL).andReturn({ + "responseText": TOOL_RESPONSE, + "status": 200 + }); + + // toolsmanager - get action function spy + let toolsManagerSpy = jasmine.createSpyObj(['getActionFunction']); + toolsManagerSpy.getActionFunction.and.returnValue(new ActionFunction({ + path: TOOL_URL + })); + + registry = new FunctionRegistry(toolsManagerSpy); + }) + afterEach(function() { + jasmine.Ajax.uninstall(); + }); + + it("returns the result via promise", async () => { + // Call the target object + const functionResponse = registry.call(CONVERSION_FUNCTION_ID, PARAMETERS_INPUT); + + // Check the expected results + await expectAsync(functionResponse).toBeResolvedTo(TOOL_RESPONSE); + }) + + it("sends a request to the tool service url", async () => { + // Call the target object + registry.call(CONVERSION_FUNCTION_ID, PARAMETERS_INPUT); + + // Check the expected results + const request = jasmine.Ajax.requests.mostRecent(); + expect(request.data()).toEqual( PARAMETERS_INPUT ); + }) + }) + + + describe("callConversion()", () => { + const TOOL_URL = "test://t1.url/toolfunction"; + const CONVERSION_FUNCTION_ID = "test-function-id"; + const PARAMETER_NAME = "testParameter1"; + const TYPE_MODEL = "type-model"; + const TYPE_METAMODEL = "type-metamodel"; + const MODEL_CONTENTS = "Parameter 1 model value"; + const METAMODEL_CONTENTS = "Parameter 2 metamodel value"; + + const TYPE_MAP_INPUT = { + [TYPE_MODEL]: MODEL_CONTENTS, + [TYPE_METAMODEL]: METAMODEL_CONTENTS + }; + + const CONVERSION_PARAM_IN = "input"; + const CONVERSION_PARAM_MM = "metamodel"; + const CONVERTED_MODEL = "Converted model contents."; + const TOOL_RESPONSE = `{"output": "${CONVERTED_MODEL}"}`; + + let registry; + + + beforeEach(()=>{ + // Setup + jasmine.Ajax.install(); + + // xhr + jasmine.Ajax.stubRequest(TOOL_URL).andReturn({ + "responseText": TOOL_RESPONSE, + "status": 200 + }); + + // toolsmanager - get action function spy + let toolsManagerSpy = jasmine.createSpyObj(['getActionFunction']); + toolsManagerSpy.getActionFunction.and.returnValue(new ActionFunction({ + parameters: [ + {name: CONVERSION_PARAM_IN, type: TYPE_MODEL, instanceOf: "metamodel"}, + {name: CONVERSION_PARAM_MM, type: TYPE_METAMODEL} + ], + path: TOOL_URL + })); + + registry = new FunctionRegistry(toolsManagerSpy); + }) + + afterEach(function() { + jasmine.Ajax.uninstall(); + }); + + it("sends a request to the tool service url", async () => { + const EXPECTED_REQUEST = { + [CONVERSION_PARAM_IN]: MODEL_CONTENTS, + [CONVERSION_PARAM_MM]: METAMODEL_CONTENTS + } + + // Call the target object + registry.callConversion(CONVERSION_FUNCTION_ID, TYPE_MAP_INPUT, PARAMETER_NAME); + + // Check the expected results + const request = jasmine.Ajax.requests.mostRecent(); + expect(request.data()).toEqual( EXPECTED_REQUEST ); + }) + + it("returns the converted result via a promise", async () => { + const EXPECTED_RESPONSE = { name: PARAMETER_NAME, data: CONVERTED_MODEL }; // Format given by utility jsonRequestConversion() + + // Call the target object + const conversionResponse = registry.callConversion(CONVERSION_FUNCTION_ID, TYPE_MAP_INPUT, PARAMETER_NAME); + + // Check the expected results + await expectAsync(conversionResponse).toBeResolvedTo(EXPECTED_RESPONSE); + }) + }) }) \ No newline at end of file diff --git a/platform/test/spec/testToolManagerSpec.js b/platform/test/spec/testToolManagerSpec.js index 9142148..f296929 100644 --- a/platform/test/spec/testToolManagerSpec.js +++ b/platform/test/spec/testToolManagerSpec.js @@ -1,8 +1,11 @@ -/*global describe, it, expect, spyOn, beforeEach, afterEach -- functions provided by Jasmine */ +/*global describe, it, expect, spyOn, beforeEach, afterEach, expectAsync -- functions provided by Jasmine */ /*global jasmine -- object provided by Jasmine */ import { ToolManager } from "../../src/ToolsManager.js"; import { TOOL_1PANELDEF_1FUNCTION } from "../resources/TestToolFiles.js"; +import { ActionFunction } from "../../src/ActionFunction.js"; +import { FunctionRegistry } from "../../src/FunctionRegistry.js"; + import "jasmine-ajax" describe("ToolManager", () => { @@ -176,4 +179,450 @@ describe("ToolManager", () => { expect(grammarImports[0].module).toEqual( "ace/mode/"+ TOOL_LANGUAGE_NAME); }) }) + + + describe( "invokeActionFunction()", ()=>{ + const ACTION_FUNCTION_ID = "function-test"; + const TOOL_LANGUAGE = "lang"; + + const PARAM1_NAME = "param1"; + const PARAM1_VALUE = "panel-1's contents"; + const PARAM1_CONVERTED_VALUE = "param1's converted contents"; + + const PARAM2_NAME = "param2"; + const PARAM2_VALUE = "param2's contents"; + const PARAM2_CONVERTED_VALUE = "param2's converted contents"; + + // types the test action functions are expecting + const ACTION_FUNCTION_PARAM1_TYPE = "type1"; + const ACTION_FUNCTION_PARAM2_TYPE = "type2"; + const ACTION_FUNCTION_RESULT= "Test function result"; + + let tm; + let functionRegistrySpy_resolve; + + beforeEach(() => { + // Setup + tm = new ToolManager(); + + // toolsmanager - functionRegister + functionRegistrySpy_resolve = spyOn(FunctionRegistry.prototype, "resolve").and.returnValue( + new ActionFunction({ + parameters: [ + {name: PARAM1_NAME, type: ACTION_FUNCTION_PARAM1_TYPE}, + {name: "language", type: "text"} + ] + }) + ); + + spyOn (FunctionRegistry.prototype, "call").and.returnValue ( + new Promise(function(resolve) { + resolve(ACTION_FUNCTION_RESULT); + }) + ); + }) + + it("returns the result via a promise", async () => { + const PARAM1_TYPE = ACTION_FUNCTION_PARAM1_TYPE; + + const parameterMap = new Map ( + [[PARAM1_NAME, {type: PARAM1_TYPE, value: PARAM1_VALUE}], + ["language", {type: "text", value: TOOL_LANGUAGE }] ] + ) + + // Call the target object + const returnedResult = tm.invokeActionFunction(ACTION_FUNCTION_ID, parameterMap); + + await expectAsync(returnedResult).toBeResolvedTo(ACTION_FUNCTION_RESULT); + }) + + it("calls functionRegister.call() with the given parameter values for matching types", async () => { + const PARAM1_TYPE = ACTION_FUNCTION_PARAM1_TYPE; + + const parameterMap = new Map ( + [[PARAM1_NAME, {type: PARAM1_TYPE, value: PARAM1_VALUE}], + ["language", {type: "text", value: TOOL_LANGUAGE }] ] + ) + + // Call the target object + await tm.invokeActionFunction(ACTION_FUNCTION_ID, parameterMap); + + // Check the expected results + const EXPECTED_PARAM_VALUES = { + [PARAM1_NAME]: PARAM1_VALUE, + "language": TOOL_LANGUAGE + } + + expect(tm.functionRegister.call).toHaveBeenCalledWith(ACTION_FUNCTION_ID, EXPECTED_PARAM_VALUES); + }) + + it("calls functionRegister.call() with the converted parameter values for non-matching types", async () => { + const PARAM1_TYPE = "typex"; + + const parameterMap = new Map ( + [[PARAM1_NAME, {type: PARAM1_TYPE, value: PARAM1_VALUE}], + ["language", {type: "text", value: TOOL_LANGUAGE }] ] + ) + + spyOn( ToolManager.prototype, "convert").and.returnValue( + new Promise(function(resolve) { + resolve( {name: PARAM1_NAME, data: PARAM1_CONVERTED_VALUE} ); + }) + ); + + // Call the target object + await tm.invokeActionFunction(ACTION_FUNCTION_ID, parameterMap); + + // Check the expected results + const EXPECTED_PARAM_VALUES = { + [PARAM1_NAME]: PARAM1_CONVERTED_VALUE, + "language": TOOL_LANGUAGE + } + + expect(tm.convert).toHaveBeenCalledWith(PARAM1_VALUE, PARAM1_TYPE, ACTION_FUNCTION_PARAM1_TYPE, PARAM1_NAME); + expect(tm.functionRegister.call).toHaveBeenCalledWith(ACTION_FUNCTION_ID, EXPECTED_PARAM_VALUES); + }) + + it("calls functionRegister.call() with the converted parameter values for non-matching types including a metamodel", async () => { + const PARAM1_TYPE = "typex"; + const PARAM2_TYPE = "typez"; + + const parameterMap = new Map ( // input to invokeActionFunction + [[PARAM1_NAME, {type: PARAM1_TYPE, value: PARAM1_VALUE}], + [PARAM2_NAME, {type: PARAM2_TYPE, value: PARAM2_VALUE}], + ["language", {type: "text", value: TOOL_LANGUAGE }] ] + ) + + // toolsmanager - functionRegister + functionRegistrySpy_resolve.and.returnValue( + new ActionFunction({ + parameters: [ + {name: PARAM1_NAME, type: ACTION_FUNCTION_PARAM1_TYPE, instanceOf: PARAM2_NAME}, + {name: PARAM2_NAME, type: ACTION_FUNCTION_PARAM2_TYPE}, + {name: "language", type: "text"} + ] + }) + ); + + // toolsmanager - conversion function spies + spyOn( ToolManager.prototype, "convertIncludingMetamodel").and.returnValue( + new Promise(function(resolve) { + resolve( {name: PARAM1_NAME, data: PARAM1_CONVERTED_VALUE} ); + }) + ); + + spyOn( ToolManager.prototype, "convert").and.returnValue( + new Promise(function(resolve) { + resolve( {name: PARAM2_NAME, data: PARAM2_CONVERTED_VALUE} ); + }) + ); + + // Call the target object + await tm.invokeActionFunction(ACTION_FUNCTION_ID, parameterMap); + + // Check the expected results + const EXPECTED_PARAM_VALUES = { + [PARAM1_NAME]: PARAM1_CONVERTED_VALUE, + [PARAM2_NAME]: PARAM2_CONVERTED_VALUE, + "language": TOOL_LANGUAGE + } + + expect(tm.convert).toHaveBeenCalledWith(PARAM2_VALUE, PARAM2_TYPE, ACTION_FUNCTION_PARAM2_TYPE, PARAM2_NAME); + + expect(tm.convertIncludingMetamodel).toHaveBeenCalledWith(PARAM1_VALUE, PARAM1_TYPE, + PARAM2_VALUE, PARAM2_TYPE, + ACTION_FUNCTION_PARAM1_TYPE, PARAM1_NAME); + + expect(tm.functionRegister.call).toHaveBeenCalledWith(ACTION_FUNCTION_ID, EXPECTED_PARAM_VALUES); + }) + + it("sends requests to a conversion function's url without unused parameters", async () => { + const PARAM1_TYPE = ACTION_FUNCTION_PARAM1_TYPE; + + const parameterMap = new Map ( + [[PARAM1_NAME, {type: PARAM1_TYPE, value: PARAM1_VALUE}], + ["language", {type: "text", value: TOOL_LANGUAGE }] ] + ) + + // toolsmanager - functionRegister + functionRegistrySpy_resolve.and.returnValue( + new ActionFunction({ + parameters: [ + {name: PARAM1_NAME, type: ACTION_FUNCTION_PARAM1_TYPE, instanceOf: PARAM2_NAME}, + {name: PARAM2_NAME, type: ACTION_FUNCTION_PARAM2_TYPE}, + {name: "language", type: "text"} + ] + }) + ); + + // Call the target object + await tm.invokeActionFunction(ACTION_FUNCTION_ID, parameterMap); + + // Check the expected results + const EXPECTED_PARAM_VALUES = { + [PARAM1_NAME]: PARAM1_VALUE, + "language": TOOL_LANGUAGE + } + + expect(tm.functionRegister.call).toHaveBeenCalledWith(ACTION_FUNCTION_ID, EXPECTED_PARAM_VALUES); + }) + + }) + + + describe("selectConversionFunctionConvertMetamodel()", () => { + let tm; + + const SOURCE_TYPE = "test-source-type"; + const FILE_CONTENTS = "Test file contents."; + const MM_FILE_CONTENTS = "Test metamodel file contents." + const MM_TARGET_TYPE = "test-metamodel-target-type"; + const PARAM_NAME = "test"; + + const CONVERSION_FUNCTION_ID = "conversion-function-id"; + const X_FUNCTION_ID = "x-function-id"; // Not interested + + let functionRegistrySpy_find; + + beforeEach( () => { + // Setup + tm = new ToolManager(); + + // toolsmanager - functionRegister + functionRegistrySpy_find = spyOn(FunctionRegistry.prototype, "find"); + + spyOn(ToolManager.prototype, "getActionFunction").and.callFake( (functionId) => { + let actionFunctionConfig; + + switch (functionId){ + case CONVERSION_FUNCTION_ID: + actionFunctionConfig = { + parameters: [ + {name: "input", type: SOURCE_TYPE, instanceOf: "metamodel"}, + {name: "metamodel", type: MM_TARGET_TYPE} + ] + }; + break; + + case X_FUNCTION_ID: + actionFunctionConfig = { + parameters: [ + {name: "input", type: SOURCE_TYPE, instanceOf: "metamodel"}, + {name: "metamodel", type: "X"} + ] + } + break; + default: + actionFunctionConfig = null; + } + + return new ActionFunction(actionFunctionConfig) + }) + }) + + it("returns a function id if a conversion is possible without considering the metamodel", async () => { + const CONSIDER_MM = false; + let mm_type = MM_TARGET_TYPE; + const typeValueMap = { [SOURCE_TYPE]: FILE_CONTENTS } + + // Call the target object + let selectConversionResult = await tm.selectConversionFunctionConvertMetamodel(mm_type, MM_FILE_CONTENTS, [CONVERSION_FUNCTION_ID, X_FUNCTION_ID], CONSIDER_MM, PARAM_NAME, typeValueMap) + + // Check the expected results + expect(selectConversionResult).toEqual(CONVERSION_FUNCTION_ID); + }) + + it("returns null if a conversion is not possible without considering the metamodel", async () => { + const CONSIDER_MM = false; + let mm_type = MM_TARGET_TYPE; + const typeValueMap = { [SOURCE_TYPE]: FILE_CONTENTS } + + // Call the target object + let selectConversionResult = await tm.selectConversionFunctionConvertMetamodel(mm_type, MM_FILE_CONTENTS, [X_FUNCTION_ID, X_FUNCTION_ID], CONSIDER_MM, PARAM_NAME, typeValueMap) + + // Check the expected results + expect(selectConversionResult).toEqual(null); + }) + + it("returns a function id, converts the metamodel, and adds the converted metamodel value to the typeValueMap if a conversion is possible considering the metamodel", async ()=>{ + const CONSIDER_MM = true; + let mm_type = "test-metamodel-type"; + const typeValueMap = { [SOURCE_TYPE]: FILE_CONTENTS } + + const MM_CONVERSION_FUNCTION_ID = "metamodel-conversion-function-id"; + const MM_CONVERTED_CONTENTS = "Test converted metamodel contents."; + + const callConversionReturn = new Promise(function(resolve) { + resolve({data: MM_CONVERTED_CONTENTS}); + }) + spyOn( FunctionRegistry.prototype, "callConversion").and.returnValue( + callConversionReturn); + + functionRegistrySpy_find.and.returnValues(null, MM_CONVERSION_FUNCTION_ID); // Find possible conversion on the second call + + // Call the target object + let selectConversionResult = await tm.selectConversionFunctionConvertMetamodel(mm_type, MM_FILE_CONTENTS, [CONVERSION_FUNCTION_ID, X_FUNCTION_ID], CONSIDER_MM, PARAM_NAME, typeValueMap) + + // Check the expected results + expect(tm.functionRegister.callConversion).toHaveBeenCalledWith( + MM_CONVERSION_FUNCTION_ID, { [mm_type]: MM_FILE_CONTENTS}, PARAM_NAME + ); + + expect(typeValueMap[MM_TARGET_TYPE]).toEqual(MM_CONVERTED_CONTENTS); + + expect(selectConversionResult).toEqual(CONVERSION_FUNCTION_ID); + }) + + it("returns null if no conversion is available considering the metamodel", async () => { + const CONSIDER_MM = true; + let mm_type = "test-metamodel-type"; + const typeValueMap = { [SOURCE_TYPE]: FILE_CONTENTS } + + spyOn( FunctionRegistry.prototype, "callConversion"); + + functionRegistrySpy_find.and.returnValues(null, null); // Do not find possible conversion + + // Call the target object + let selectConversionResult = await tm.selectConversionFunctionConvertMetamodel(mm_type, MM_FILE_CONTENTS, [CONVERSION_FUNCTION_ID, X_FUNCTION_ID], CONSIDER_MM, PARAM_NAME, typeValueMap) + + // Check the expected results + expect(tm.functionRegister.callConversion).not.toHaveBeenCalled(); + + expect(typeValueMap[MM_TARGET_TYPE]).toEqual(undefined); + + expect(selectConversionResult).toEqual(null); + }) + }) + + + describe("convert()", () => { + let tm; + let findConversionSpy; + + const FILE_CONTENTS = "Test file contents."; + const SOURCE_TYPE = "test-source-type"; + const TARGET_TYPE = "test-target-type"; + const PARAM_NAME = "test"; + const callConversionReturn = new Promise(function(resolve) { + resolve(true); + }) + + const CONVERSION_FUNCTION_ID = "conversion-function-id"; + + beforeEach(()=>{ + // Setup + findConversionSpy = spyOn( FunctionRegistry.prototype, "find"). + and.returnValue(CONVERSION_FUNCTION_ID); + + spyOn( FunctionRegistry.prototype, "callConversion").and.returnValue( + callConversionReturn); + + + const educationPlatformSpy = jasmine.createSpyObj(['errorNotification']); + + tm = new ToolManager(educationPlatformSpy.errorNotification); + }) + + it("calls functionRegistry_callConversion on a conversion function being available", ()=>{ + // Call the target object + tm.convert(FILE_CONTENTS, SOURCE_TYPE, TARGET_TYPE, PARAM_NAME); + + // Check the expected results + expect(tm.functionRegister.callConversion).toHaveBeenCalledWith( + CONVERSION_FUNCTION_ID, { [SOURCE_TYPE]: FILE_CONTENTS } , PARAM_NAME + ); + + expect(tm.errorNotification).not.toHaveBeenCalled(); + }) + + it("returns a promise on a conversion function being available", ()=> { + // Call the target object + const convertResult = tm.convert(FILE_CONTENTS, SOURCE_TYPE, TARGET_TYPE, PARAM_NAME); + + // Check the expected results + expect(convertResult).toEqual(callConversionReturn); + }) + + it("returns null and provides an error notification on a conversion function not being available", ()=> { + findConversionSpy.and.returnValue(null); + + // Call the target object + const convertResult = tm.convert(FILE_CONTENTS, SOURCE_TYPE, TARGET_TYPE, PARAM_NAME); + + // Check the expected results + expect(convertResult).toEqual(null); + expect(tm.errorNotification).toHaveBeenCalledWith(jasmine.stringMatching("(N|n)o conversion function")) + }) + + }) + + + describe("convertIncludingMetamodel()", () => { + let tm; + let findConversionSpy; + + const FILE_CONTENTS = "Test file contents."; + const SOURCE_TYPE = "test-source-type"; + const TARGET_TYPE = "test-target-type"; + const MM_FILE_CONTENTS = "Test metamodel file contents." + const MM_TYPE = "test-metamodel-type"; + const PARAM_NAME = "test"; + const callConversionReturn = new Promise(function(resolve) { + resolve(true); + }) + + const CONVERSION_FUNCTION_ID = "conversion-function-id"; + + beforeEach(()=>{ + // Setup + findConversionSpy = spyOn( FunctionRegistry.prototype, "findPartial"). + and.returnValue([CONVERSION_FUNCTION_ID]); + + spyOn( FunctionRegistry.prototype, "callConversion").and.returnValue( + callConversionReturn); + + // toolsmanager - get action function spy + spyOn(ToolManager.prototype, "getActionFunction").and.returnValue(new ActionFunction({ + parameters: [ + {name: "input", type: SOURCE_TYPE, instanceOf: "metamodel"}, + {name: "metamodel", type: MM_TYPE} + ] + })); + + const educationPlatformSpy = jasmine.createSpyObj(['errorNotification']); + + tm = new ToolManager(educationPlatformSpy.errorNotification); + }) + + it("calls functionRegister.callConversion() on a conversion function being available", async ()=> { + // Call the target object + await tm.convertIncludingMetamodel(FILE_CONTENTS, SOURCE_TYPE, MM_FILE_CONTENTS, MM_TYPE, TARGET_TYPE, PARAM_NAME); + + // Check the expected results + expect(tm.functionRegister.callConversion).toHaveBeenCalledWith( + CONVERSION_FUNCTION_ID, {[SOURCE_TYPE]: FILE_CONTENTS, [MM_TYPE]: MM_FILE_CONTENTS } , PARAM_NAME + ); + + expect(tm.errorNotification).not.toHaveBeenCalled(); + }) + + it("returns a promise on a conversion function being available", async () => { + // Call the target object + const convertResult = tm.convertIncludingMetamodel(FILE_CONTENTS, SOURCE_TYPE, MM_FILE_CONTENTS, MM_TYPE, TARGET_TYPE, PARAM_NAME); + + // Check the expected results + await expectAsync(convertResult).toBePending(); + }) + + it("returns null and provides an error notification on a conversion function not being available", async () => { + findConversionSpy.and.returnValue(null); + + // Call the target object + const convertResult = await tm.convertIncludingMetamodel(FILE_CONTENTS, SOURCE_TYPE, MM_FILE_CONTENTS, MM_TYPE, TARGET_TYPE, PARAM_NAME); + + // Check the expected results + expect(convertResult).toEqual(null); + expect(tm.errorNotification).toHaveBeenCalledWith(jasmine.stringMatching("(N|n)o conversion function")) + }) + }) })