From 078bb981696f25a7be99bc3030110cdec2e7efa7 Mon Sep 17 00:00:00 2001 From: Jonas Lagoni Date: Thu, 28 Apr 2022 15:32:16 +0200 Subject: [PATCH] refactor: convert Go to new constraint setup (#732) * Convert Go to the new setup * Add extra test --- src/generators/go/GoConstrainer.ts | 243 ++++-------------- src/generators/go/GoGenerator.ts | 16 +- .../go/constrainer/EnumConstrainer.ts | 53 ++-- .../go/constrainer/ModelNameConstrainer.ts | 53 ++-- .../go/constrainer/PropertyKeyConstrainer.ts | 61 ++--- test/TestUtils/TestRenderers.ts | 3 + test/generators/go/GoConstrainer.spec.ts | 103 ++++++++ .../go/constrainer/EnumConstrainer.spec.ts | 99 +++++++ .../constrainer/ModelNameConstrainer.spec.ts | 57 ++++ .../PropertyKeyConstrainer.spec.ts | 70 +++++ 10 files changed, 447 insertions(+), 311 deletions(-) create mode 100644 test/generators/go/GoConstrainer.spec.ts create mode 100644 test/generators/go/constrainer/EnumConstrainer.spec.ts create mode 100644 test/generators/go/constrainer/ModelNameConstrainer.spec.ts create mode 100644 test/generators/go/constrainer/PropertyKeyConstrainer.spec.ts diff --git a/src/generators/go/GoConstrainer.ts b/src/generators/go/GoConstrainer.ts index 76352ab303..72b5d8ee64 100644 --- a/src/generators/go/GoConstrainer.ts +++ b/src/generators/go/GoConstrainer.ts @@ -1,200 +1,53 @@ -import { ConstrainedAnyModel, ConstrainedBooleanModel, ConstrainedFloatModel, ConstrainedIntegerModel, ConstrainedMetaModel, ConstrainedObjectModel, ConstrainedReferenceModel, ConstrainedStringModel, ConstrainedTupleModel, ConstrainedTupleValueModel, ConstrainedArrayModel, ConstrainedUnionModel, ConstrainedEnumModel, ConstrainedDictionaryModel, ConstrainedEnumValueModel } from '../../models/ConstrainedMetaModel'; -import { AnyModel, BooleanModel, FloatModel, IntegerModel, MetaModel, ObjectModel, ReferenceModel, StringModel, TupleModel, ArrayModel, UnionModel, EnumModel, DictionaryModel } from '../../models/MetaModel'; -import { defaultPropertyKeyConstraints, PropertyKeyConstraintType } from './constrainer/PropertyKeyConstrainer'; -import { defaultModelNameConstraints, ModelNameConstraintType } from './constrainer/ModelNameConstrainer'; -import { defaultEnumKeyConstraints, EnumConstraintType } from './constrainer/EnumConstrainer'; - -export interface GoConstraints { - enumKey: EnumConstraintType, - modelName: ModelNameConstraintType, - propertyKey: PropertyKeyConstraintType, -} +import { TypeMapping } from '../../helpers'; +import { defaultEnumKeyConstraints, defaultEnumValueConstraints } from './constrainer/EnumConstrainer'; +import { defaultModelNameConstraints } from './constrainer/ModelNameConstrainer'; +import { defaultPropertyKeyConstraints } from './constrainer/PropertyKeyConstrainer'; +import { GoRenderer } from './GoRenderer'; + +export const GoDefaultTypeMapping: TypeMapping = { + Object ({constrainedModel}): string { + return constrainedModel.name; + }, + Reference ({constrainedModel}): string { + return constrainedModel.name; + }, + Any (): string { + return 'interface{}'; + }, + Float (): string { + return 'float64'; + }, + Integer (): string { + return 'int'; + }, + String (): string { + return 'string'; + }, + Boolean (): string { + return 'bool'; + }, + Tuple (): string { + //Because Java have no notion of tuples (and no custom implementation), we have to render it as a list of any value. + return '[]interface{}'; + }, + Array ({constrainedModel}): string { + return `[]${constrainedModel.valueModel.type}`; + }, + Enum ({constrainedModel}): string { + return constrainedModel.name; + }, + Union (): string { + //Because Java have no notion of unions (and no custom implementation), we have to render it as any value. + return 'interface{}'; + }, + Dictionary ({constrainedModel}): string { + return `map[${constrainedModel.key.type}]${constrainedModel.value.type}`; + } +}; -export const DefaultGoConstraints: GoConstraints = { +export const GoDefaultConstraints = { enumKey: defaultEnumKeyConstraints(), + enumValue: defaultEnumValueConstraints(), modelName: defaultModelNameConstraints(), propertyKey: defaultPropertyKeyConstraints() }; - -type TypeMappingFunction = (model: T) => string; - -type TypeMapping = { - Object?: TypeMappingFunction, - Reference?: TypeMappingFunction, - Any?: TypeMappingFunction, - Float?: TypeMappingFunction, - Integer?: TypeMappingFunction, - String?: TypeMappingFunction, - Boolean?: TypeMappingFunction, - Tuple?: TypeMappingFunction, - Array?: TypeMappingFunction, - Enum?: TypeMappingFunction, - Union?: TypeMappingFunction, - Dictionary?: TypeMappingFunction -} - -function constrainReferenceModel(constrainedName: string, metaModel: ReferenceModel, typeMapping: TypeMapping, constrainRules: GoConstraints): ConstrainedReferenceModel { - const constrainedRefModel = constrainMetaModel(metaModel.ref, typeMapping, constrainRules); - const constrainedModel = new ConstrainedReferenceModel(constrainedName, metaModel.originalInput, '', constrainedRefModel); - if (typeMapping.Reference !== undefined) { - constrainedModel.type = typeMapping.Reference(constrainedModel); - } else { - constrainedModel.type = constrainedModel.name; - } - return constrainedModel; -} -function constrainAnyModel(constrainedName: string, metaModel: AnyModel, typeMapping: TypeMapping): ConstrainedAnyModel { - const constrainedModel = new ConstrainedAnyModel(constrainedName, metaModel.originalInput, ''); - if (typeMapping.Any !== undefined) { - constrainedModel.type = typeMapping.Any(constrainedModel); - } else { - constrainedModel.type = 'interface{}'; - } - return constrainedModel; -} -function constrainFloatModel(constrainedName: string, metaModel: FloatModel, typeMapping: TypeMapping): ConstrainedFloatModel { - const constrainedModel = new ConstrainedFloatModel(constrainedName, metaModel.originalInput, ''); - if (typeMapping.Float !== undefined) { - constrainedModel.type = typeMapping.Float(constrainedModel); - } else { - constrainedModel.type = 'float64'; - } - return constrainedModel; -} -function constrainIntegerModel(constrainedName: string, metaModel: IntegerModel, typeMapping: TypeMapping): ConstrainedIntegerModel { - const constrainedModel = new ConstrainedIntegerModel(constrainedName, metaModel.originalInput, ''); - if (typeMapping.Integer !== undefined) { - constrainedModel.type = typeMapping.Integer(constrainedModel); - } else { - constrainedModel.type = 'int'; - } - return constrainedModel; -} -function constrainStringModel(constrainedName: string, metaModel: IntegerModel, typeMapping: TypeMapping): ConstrainedIntegerModel { - const constrainedModel = new ConstrainedStringModel(constrainedName, metaModel.originalInput, ''); - if (typeMapping.String !== undefined) { - constrainedModel.type = typeMapping.String(constrainedModel); - } else { - constrainedModel.type = 'string'; - } - return constrainedModel; -} -function constrainBooleanModel(constrainedName: string, metaModel: BooleanModel, typeMapping: TypeMapping): ConstrainedBooleanModel { - const constrainedModel = new ConstrainedBooleanModel(constrainedName, metaModel.originalInput, ''); - if (typeMapping.Boolean !== undefined) { - constrainedModel.type = typeMapping.Boolean(constrainedModel); - } else { - constrainedModel.type = 'bool'; - } - return constrainedModel; -} -function constrainTupleModel(constrainedName: string, metaModel: TupleModel, typeMapping: TypeMapping, constrainRules: GoConstraints): ConstrainedTupleModel { - const constrainedTupleModels = metaModel.tuple.map((tupleValue) => { - const tupleType = constrainMetaModel(tupleValue.value, typeMapping, constrainRules); - return new ConstrainedTupleValueModel(tupleValue.index, tupleType); - }); - const constrainedModel = new ConstrainedTupleModel(constrainedName, metaModel.originalInput, '', constrainedTupleModels); - if (typeMapping.Tuple !== undefined) { - constrainedModel.type = typeMapping.Tuple(constrainedModel); - } else { - const tupleType = '[]interface{}'; - constrainedModel.type = tupleType; - } - return constrainedModel; -} -function constrainArrayModel(constrainedName: string, metaModel: ArrayModel, typeMapping: TypeMapping, constrainRules: GoConstraints): ConstrainedArrayModel { - const constrainedValueModel = constrainMetaModel(metaModel.valueModel, typeMapping, constrainRules); - const constrainedModel = new ConstrainedArrayModel(constrainedName, metaModel.originalInput, '', constrainedValueModel); - if (typeMapping.Array !== undefined) { - constrainedModel.type = typeMapping.Array(constrainedModel); - } else { - constrainedModel.type = `[]${constrainedValueModel.type}`; - } - return constrainedModel; -} -function constrainUnionModel(constrainedName: string, metaModel: UnionModel, typeMapping: TypeMapping, constrainRules: GoConstraints): ConstrainedUnionModel { - const constrainedUnionModels = metaModel.union.map((unionValue) => { - return constrainMetaModel(unionValue, typeMapping, constrainRules); - }); - const constrainedModel = new ConstrainedUnionModel(constrainedName, metaModel.originalInput, '', constrainedUnionModels); - if (typeMapping.Union !== undefined) { - constrainedModel.type = typeMapping.Union(constrainedModel); - } else { - constrainedModel.type = 'interface{}'; - } - return constrainedModel; -} -function constrainDictionaryModel(constrainedName: string, metaModel: DictionaryModel, typeMapping: TypeMapping, constrainRules: GoConstraints): ConstrainedDictionaryModel { - const keyModel = constrainMetaModel(metaModel.key, typeMapping, constrainRules); - const valueModel = constrainMetaModel(metaModel.value, typeMapping, constrainRules); - const constrainedModel = new ConstrainedDictionaryModel(constrainedName, metaModel.originalInput, '', keyModel, valueModel, metaModel.serializationType); - if (typeMapping.Dictionary !== undefined) { - constrainedModel.type = typeMapping.Dictionary(constrainedModel); - } else { - const type = `map[${keyModel.type}]${valueModel.type}`; - constrainedModel.type = type; - } - return constrainedModel; -} - -function constrainObjectModel(constrainedName: string, objectModel: ObjectModel, typeMapping: TypeMapping, constrainRules: GoConstraints): ConstrainedObjectModel { - const constrainedObjectModel = new ConstrainedObjectModel(constrainedName, objectModel.originalInput, '', {}); - for (const [propertyKey, propertyMetaModel] of Object.entries(objectModel.properties)) { - const constrainedPropertyName = constrainRules.propertyKey({propertyKey, constrainedObjectModel, objectModel}); - const constrainedProperty = constrainMetaModel(propertyMetaModel, typeMapping, constrainRules); - constrainedObjectModel.properties[String(constrainedPropertyName)] = constrainedProperty; - } - if (typeMapping.Object !== undefined) { - constrainedObjectModel.type = typeMapping.Object(constrainedObjectModel); - } else { - constrainedObjectModel.type = constrainedName; - } - return constrainedObjectModel; -} - -export function ConstrainEnumModel(constrainedName: string, enumModel: EnumModel, typeMapping: TypeMapping, constrainRules: GoConstraints): ConstrainedEnumModel { - const constrainedModel = new ConstrainedEnumModel(constrainedName, enumModel.originalInput, '', []); - - for (const enumValue of enumModel.values) { - const constrainedEnumKey = constrainRules.enumKey({enumKey: String(enumValue.key), enumModel, constrainedEnumModel: constrainedModel}); - const constrainedEnumValueModel = new ConstrainedEnumValueModel(constrainedEnumKey, enumValue.value); - constrainedModel.values.push(constrainedEnumValueModel); - } - if (typeMapping.Enum !== undefined) { - constrainedModel.type = typeMapping.Enum(constrainedModel); - } else { - constrainedModel.type = constrainedName; - } - return constrainedModel; -} - -export function constrainMetaModel(metaModel: MetaModel, typeMapping: TypeMapping, constrainRules: GoConstraints): ConstrainedMetaModel { - const constrainedName = constrainRules.modelName({modelName: metaModel.name}); - - if (metaModel instanceof ObjectModel) { - return constrainObjectModel(constrainedName, metaModel, typeMapping, constrainRules); - } else if (metaModel instanceof ReferenceModel) { - return constrainReferenceModel(constrainedName, metaModel, typeMapping, constrainRules); - } else if (metaModel instanceof AnyModel) { - return constrainAnyModel(constrainedName, metaModel, typeMapping); - } else if (metaModel instanceof FloatModel) { - return constrainFloatModel(constrainedName, metaModel, typeMapping); - } else if (metaModel instanceof IntegerModel) { - return constrainIntegerModel(constrainedName, metaModel, typeMapping); - } else if (metaModel instanceof StringModel) { - return constrainStringModel(constrainedName, metaModel, typeMapping); - } else if (metaModel instanceof BooleanModel) { - return constrainBooleanModel(constrainedName, metaModel, typeMapping); - } else if (metaModel instanceof TupleModel) { - return constrainTupleModel(constrainedName, metaModel, typeMapping, constrainRules); - } else if (metaModel instanceof ArrayModel) { - return constrainArrayModel(constrainedName, metaModel, typeMapping, constrainRules); - } else if (metaModel instanceof UnionModel) { - return constrainUnionModel(constrainedName, metaModel, typeMapping, constrainRules); - } else if (metaModel instanceof EnumModel) { - return ConstrainEnumModel(constrainedName, metaModel, typeMapping, constrainRules); - } else if (metaModel instanceof DictionaryModel) { - return constrainDictionaryModel(constrainedName, metaModel, typeMapping, constrainRules); - } - throw new Error('Could not constrain model'); -} diff --git a/src/generators/go/GoGenerator.ts b/src/generators/go/GoGenerator.ts index 991545e736..0dba394172 100644 --- a/src/generators/go/GoGenerator.ts +++ b/src/generators/go/GoGenerator.ts @@ -4,13 +4,15 @@ import { defaultGeneratorOptions } from '../AbstractGenerator'; import { CommonModel, CommonInputModel, RenderOutput } from '../../models'; -import { TypeHelpers, ModelKind, FormatHelpers } from '../../helpers'; +import { TypeHelpers, ModelKind, FormatHelpers, Constraints, TypeMapping } from '../../helpers'; import { GoPreset, GO_DEFAULT_PRESET } from './GoPreset'; import { StructRenderer } from './renderers/StructRenderer'; import { EnumRenderer } from './renderers/EnumRenderer'; import { pascalCaseTransformMerge } from 'change-case'; import { Logger } from '../../utils/LoggingInterface'; import { isReservedGoKeyword } from './Constants'; +import { GoDefaultConstraints, GoDefaultTypeMapping } from './GoConstrainer'; +import { GoRenderer } from './GoRenderer'; /** * The Go naming convention type */ @@ -48,6 +50,8 @@ export const GoNamingConventionImplementation: GoNamingConvention = { export interface GoOptions extends CommonGeneratorOptions { namingConvention?: GoNamingConvention; + typeMapping: TypeMapping; + constraints: Constraints } export interface GoRenderCompleteModelOptions { @@ -61,12 +65,16 @@ export class GoGenerator extends AbstractGenerator = GoGenerator.defaultOptions, ) { - super('Go', GoGenerator.defaultOptions, options); + const mergedOptions = {...GoGenerator.defaultOptions, ...options}; + + super('Go', GoGenerator.defaultOptions, mergedOptions); } reservedGoKeyword(name: string): boolean { return isReservedGoKeyword(name); diff --git a/src/generators/go/constrainer/EnumConstrainer.ts b/src/generators/go/constrainer/EnumConstrainer.ts index d9195e35ed..ce4e03c733 100644 --- a/src/generators/go/constrainer/EnumConstrainer.ts +++ b/src/generators/go/constrainer/EnumConstrainer.ts @@ -1,7 +1,7 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ import { ConstrainedEnumModel, EnumModel } from '../../../models'; -import { NO_NUMBER_START_CHAR, NO_DUPLICATE_ENUM_KEYS, NO_EMPTY_VALUE} from '../../../helpers/Constraints'; -import { FormatHelpers } from '../../../helpers'; +import { NO_NUMBER_START_CHAR, NO_DUPLICATE_ENUM_KEYS, NO_EMPTY_VALUE, NO_RESERVED_KEYWORDS } from '../../../helpers/Constraints'; +import { FormatHelpers, EnumKeyConstraint, EnumValueConstraint } from '../../../helpers'; +import { isReservedGoKeyword } from '../Constants'; export type ModelEnumKeyConstraints = { NO_SPECIAL_CHAR: (value: string) => string; @@ -22,44 +22,27 @@ export const DefaultEnumKeyConstraints: ModelEnumKeyConstraints = { NO_EMPTY_VALUE, NAMING_FORMATTER: FormatHelpers.toConstantCase, NO_RESERVED_KEYWORDS: (value: string) => { - return value; + return NO_RESERVED_KEYWORDS(value, isReservedGoKeyword); } }; -export type EnumKeyContext = { - enumKey: string, - constrainedEnumModel: ConstrainedEnumModel, - enumModel: EnumModel -} -export type EnumConstraintType = (context: EnumKeyContext, constraints?: ModelEnumKeyConstraints) => string; +export function defaultEnumKeyConstraints(customConstraints?: Partial): EnumKeyConstraint { + const constraints = {...DefaultEnumKeyConstraints, ...customConstraints}; -export function defaultEnumKeyConstraints(customConstraints?: ModelEnumKeyConstraints): EnumConstraintType { - const constraints = DefaultEnumKeyConstraints; - if (customConstraints !== undefined) { - if (customConstraints.NAMING_FORMATTER !== undefined) { - constraints.NAMING_FORMATTER = customConstraints.NAMING_FORMATTER; - } - if (customConstraints.NO_SPECIAL_CHAR !== undefined) { - constraints.NO_SPECIAL_CHAR = customConstraints.NO_SPECIAL_CHAR; - } - if (customConstraints.NO_NUMBER_START_CHAR !== undefined) { - constraints.NO_NUMBER_START_CHAR = customConstraints.NO_NUMBER_START_CHAR; - } - if (customConstraints.NO_RESERVED_KEYWORDS !== undefined) { - constraints.NO_RESERVED_KEYWORDS = customConstraints.NO_RESERVED_KEYWORDS; - } - if (customConstraints.NO_DUPLICATE_KEYS !== undefined) { - constraints.NO_DUPLICATE_KEYS = customConstraints.NO_DUPLICATE_KEYS; - } - } return ({enumKey, enumModel, constrainedEnumModel}) => { let constrainedEnumKey = enumKey; - constrainedEnumKey = constraints.NO_SPECIAL_CHAR!(constrainedEnumKey); - constrainedEnumKey = constraints.NO_NUMBER_START_CHAR!(constrainedEnumKey); - constrainedEnumKey = constraints.NO_EMPTY_VALUE!(constrainedEnumKey); - constrainedEnumKey = constraints.NO_RESERVED_KEYWORDS!(constrainedEnumKey); - constrainedEnumKey = constraints.NAMING_FORMATTER!(constrainedEnumKey); - constrainedEnumKey = constraints.NO_DUPLICATE_KEYS!(constrainedEnumModel, enumModel, constrainedEnumKey, constraints.NAMING_FORMATTER!); + constrainedEnumKey = constraints.NO_SPECIAL_CHAR(constrainedEnumKey); + constrainedEnumKey = constraints.NO_NUMBER_START_CHAR(constrainedEnumKey); + constrainedEnumKey = constraints.NO_EMPTY_VALUE(constrainedEnumKey); + constrainedEnumKey = constraints.NO_RESERVED_KEYWORDS(constrainedEnumKey); + constrainedEnumKey = constraints.NAMING_FORMATTER(constrainedEnumKey); + constrainedEnumKey = constraints.NO_DUPLICATE_KEYS(constrainedEnumModel, enumModel, constrainedEnumKey, constraints.NAMING_FORMATTER); return constrainedEnumKey; }; } + +export function defaultEnumValueConstraints(): EnumValueConstraint { + return ({enumValue}) => { + return `"${enumValue}"`; + }; +} diff --git a/src/generators/go/constrainer/ModelNameConstrainer.ts b/src/generators/go/constrainer/ModelNameConstrainer.ts index 21c4bc4df2..f8d8e57894 100644 --- a/src/generators/go/constrainer/ModelNameConstrainer.ts +++ b/src/generators/go/constrainer/ModelNameConstrainer.ts @@ -1,13 +1,13 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -import { NO_NUMBER_START_CHAR, NO_EMPTY_VALUE} from '../../../helpers/Constraints'; -import { FormatHelpers } from '../../../helpers'; +import { NO_NUMBER_START_CHAR, NO_EMPTY_VALUE, NO_RESERVED_KEYWORDS } from '../../../helpers/Constraints'; +import { FormatHelpers, ModelNameConstraint } from '../../../helpers'; +import { isReservedGoKeyword } from '../Constants'; export type ModelNameConstraints = { - NO_SPECIAL_CHAR?: (value: string) => string; - NO_NUMBER_START_CHAR?: (value: string) => string; - NO_EMPTY_VALUE?: (value: string) => string; - NAMING_FORMATTER?: (value: string) => string; - NO_RESERVED_KEYWORDS?: (value: string) => string; + NO_SPECIAL_CHAR: (value: string) => string; + NO_NUMBER_START_CHAR: (value: string) => string; + NO_EMPTY_VALUE: (value: string) => string; + NAMING_FORMATTER: (value: string) => string; + NO_RESERVED_KEYWORDS: (value: string) => string; }; export const DefaultModelNameConstraints: ModelNameConstraints = { @@ -22,41 +22,20 @@ export const DefaultModelNameConstraints: ModelNameConstraints = { return FormatHelpers.toPascalCase(value); }, NO_RESERVED_KEYWORDS: (value: string) => { - return value; + return NO_RESERVED_KEYWORDS(value, isReservedGoKeyword); } }; -export type ModelNameContext = { - modelName: string -} -export type ModelNameConstraintType = (context: ModelNameContext) => string; -export function defaultModelNameConstraints(customConstraints?: ModelNameConstraints): ModelNameConstraintType { - const constraints = DefaultModelNameConstraints; - if (customConstraints !== undefined) { - if (customConstraints.NO_SPECIAL_CHAR !== undefined) { - constraints.NO_SPECIAL_CHAR = customConstraints.NO_SPECIAL_CHAR; - } - if (customConstraints.NO_NUMBER_START_CHAR !== undefined) { - constraints.NO_NUMBER_START_CHAR = customConstraints.NO_NUMBER_START_CHAR; - } - if (customConstraints.NO_EMPTY_VALUE !== undefined) { - constraints.NO_EMPTY_VALUE = customConstraints.NO_EMPTY_VALUE; - } - if (customConstraints.NAMING_FORMATTER !== undefined) { - constraints.NAMING_FORMATTER = customConstraints.NAMING_FORMATTER; - } - if (customConstraints.NO_RESERVED_KEYWORDS !== undefined) { - constraints.NAMING_FORMATTER = customConstraints.NO_RESERVED_KEYWORDS; - } - } +export function defaultModelNameConstraints(customConstraints?: Partial): ModelNameConstraint { + const constraints = {...DefaultModelNameConstraints, ...customConstraints}; return ({modelName}) => { let constrainedValue = modelName; - constrainedValue = constraints.NO_SPECIAL_CHAR!(constrainedValue); - constrainedValue = constraints.NO_NUMBER_START_CHAR!(constrainedValue); - constrainedValue = constraints.NO_EMPTY_VALUE!(constrainedValue); - constrainedValue = constraints.NAMING_FORMATTER!(constrainedValue); - constrainedValue = constraints.NO_RESERVED_KEYWORDS!(constrainedValue); + constrainedValue = constraints.NO_SPECIAL_CHAR(constrainedValue); + constrainedValue = constraints.NO_NUMBER_START_CHAR(constrainedValue); + constrainedValue = constraints.NO_EMPTY_VALUE(constrainedValue); + constrainedValue = constraints.NO_RESERVED_KEYWORDS(constrainedValue); + constrainedValue = constraints.NAMING_FORMATTER(constrainedValue); return constrainedValue; }; } diff --git a/src/generators/go/constrainer/PropertyKeyConstrainer.ts b/src/generators/go/constrainer/PropertyKeyConstrainer.ts index bab657785e..bc2b32df4e 100644 --- a/src/generators/go/constrainer/PropertyKeyConstrainer.ts +++ b/src/generators/go/constrainer/PropertyKeyConstrainer.ts @@ -1,14 +1,15 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ import { ConstrainedObjectModel, ObjectModel } from '../../../models'; -import { NO_NUMBER_START_CHAR, NO_DUPLICATE_PROPERTIES, NO_EMPTY_VALUE} from '../../../helpers/Constraints'; -import { FormatHelpers } from '../../../helpers'; +import { NO_NUMBER_START_CHAR, NO_DUPLICATE_PROPERTIES, NO_EMPTY_VALUE, NO_RESERVED_KEYWORDS } from '../../../helpers/Constraints'; +import { FormatHelpers, PropertyKeyConstraint } from '../../../helpers'; +import { isReservedGoKeyword } from '../Constants'; export type PropertyKeyConstraintOptions = { - NO_SPECIAL_CHAR?: (value: string) => string; - NO_NUMBER_START_CHAR?: (value: string) => string; - NO_DUPLICATE_PROPERTIES?: (constrainedObjectModel: ConstrainedObjectModel, objectModel: ObjectModel, propertyName: string, namingFormatter: (value: string) => string) => string; - NO_EMPTY_VALUE?: (value: string) => string; - NAMING_FORMATTER?: (value: string) => string; + NO_SPECIAL_CHAR: (value: string) => string; + NO_NUMBER_START_CHAR: (value: string) => string; + NO_DUPLICATE_PROPERTIES: (constrainedObjectModel: ConstrainedObjectModel, objectModel: ObjectModel, propertyName: string, namingFormatter: (value: string) => string) => string; + NO_EMPTY_VALUE: (value: string) => string; + NAMING_FORMATTER: (value: string) => string; + NO_RESERVED_KEYWORDS: (value: string) => string; }; export const DefaultPropertyKeyConstraints: PropertyKeyConstraintOptions = { @@ -21,43 +22,23 @@ export const DefaultPropertyKeyConstraints: PropertyKeyConstraintOptions = { NO_DUPLICATE_PROPERTIES, NO_EMPTY_VALUE, NAMING_FORMATTER: FormatHelpers.toPascalCase, + NO_RESERVED_KEYWORDS: (value: string) => { + return NO_RESERVED_KEYWORDS(value, isReservedGoKeyword); + } }; -export type PropertyKeyContext = { - propertyKey: string, - constrainedObjectModel: ConstrainedObjectModel, - objectModel: ObjectModel -} - -export type PropertyKeyConstraintType = (context: PropertyKeyContext) => string; +export function defaultPropertyKeyConstraints(customConstraints?: Partial): PropertyKeyConstraint { + const constraints = {...DefaultPropertyKeyConstraints, ...customConstraints}; -export function defaultPropertyKeyConstraints(customConstraints?: PropertyKeyConstraintOptions): PropertyKeyConstraintType { - const constraints = DefaultPropertyKeyConstraints; - if (customConstraints !== undefined) { - if (customConstraints.NAMING_FORMATTER !== undefined) { - constraints.NAMING_FORMATTER = customConstraints.NAMING_FORMATTER; - } - if (customConstraints.NO_SPECIAL_CHAR !== undefined) { - constraints.NO_SPECIAL_CHAR = customConstraints.NO_SPECIAL_CHAR; - } - if (customConstraints.NO_NUMBER_START_CHAR !== undefined) { - constraints.NO_NUMBER_START_CHAR = customConstraints.NO_NUMBER_START_CHAR; - } - if (customConstraints.NO_EMPTY_VALUE !== undefined) { - constraints.NO_EMPTY_VALUE = customConstraints.NO_EMPTY_VALUE; - } - if (customConstraints.NO_DUPLICATE_PROPERTIES !== undefined) { - constraints.NO_DUPLICATE_PROPERTIES = customConstraints.NO_DUPLICATE_PROPERTIES; - } - } - return ({propertyKey, constrainedObjectModel, objectModel}: PropertyKeyContext) => { + return ({propertyKey, constrainedObjectModel, objectModel}) => { let constrainedPropertyKey = propertyKey; - constrainedPropertyKey = constraints.NO_SPECIAL_CHAR!(constrainedPropertyKey); - constrainedPropertyKey = constraints.NO_NUMBER_START_CHAR!(constrainedPropertyKey); - constrainedPropertyKey = constraints.NO_EMPTY_VALUE!(constrainedPropertyKey); - constrainedPropertyKey = constraints.NAMING_FORMATTER!(constrainedPropertyKey); - constrainedPropertyKey = constraints.NO_DUPLICATE_PROPERTIES!(constrainedObjectModel, objectModel, constrainedPropertyKey, constraints.NAMING_FORMATTER!); + constrainedPropertyKey = constraints.NO_SPECIAL_CHAR(constrainedPropertyKey); + constrainedPropertyKey = constraints.NO_NUMBER_START_CHAR(constrainedPropertyKey); + constrainedPropertyKey = constraints.NO_EMPTY_VALUE(constrainedPropertyKey); + constrainedPropertyKey = constraints.NO_RESERVED_KEYWORDS(constrainedPropertyKey); + constrainedPropertyKey = constraints.NAMING_FORMATTER(constrainedPropertyKey); + constrainedPropertyKey = constraints.NO_DUPLICATE_PROPERTIES(constrainedObjectModel, objectModel, constrainedPropertyKey, constraints.NAMING_FORMATTER); return constrainedPropertyKey; }; } diff --git a/test/TestUtils/TestRenderers.ts b/test/TestUtils/TestRenderers.ts index 590e6f17da..8c6404d431 100644 --- a/test/TestUtils/TestRenderers.ts +++ b/test/TestUtils/TestRenderers.ts @@ -1,4 +1,5 @@ import { AbstractRenderer, CommonInputModel, CommonModel, RenderOutput } from '../../src'; +import { GoRenderer } from '../../src/generators/go/GoRenderer'; import { CSharpRenderer } from '../../src/generators/csharp/CSharpRenderer'; import { JavaRenderer } from '../../src/generators/java/JavaRenderer'; import {testOptions, TestGenerator} from './TestGenerator'; @@ -13,4 +14,6 @@ export class TestRenderer extends AbstractRenderer { } export class MockJavaRenderer extends JavaRenderer {} +export class MockGoRenderer extends GoRenderer {} export class MockCSharpRenderer extends CSharpRenderer {} + diff --git a/test/generators/go/GoConstrainer.spec.ts b/test/generators/go/GoConstrainer.spec.ts new file mode 100644 index 0000000000..58a281b678 --- /dev/null +++ b/test/generators/go/GoConstrainer.spec.ts @@ -0,0 +1,103 @@ +import {GoDefaultTypeMapping } from '../../../src/generators/go/GoConstrainer'; +import {MockGoRenderer} from '../../TestUtils/TestRenderers'; +import { GoRenderer } from '../../../src/generators/go/GoRenderer'; +import { CommonInputModel, CommonModel, ConstrainedAnyModel, ConstrainedArrayModel, ConstrainedBooleanModel, ConstrainedDictionaryModel, ConstrainedEnumModel, ConstrainedFloatModel, ConstrainedIntegerModel, ConstrainedObjectModel, ConstrainedReferenceModel, ConstrainedStringModel, ConstrainedTupleModel, ConstrainedUnionModel, GoGenerator } from '../../../src'; +describe('GoConstrainer', () => { + let renderer: GoRenderer; + beforeEach(() => { + renderer = new MockGoRenderer(GoGenerator.defaultOptions, new GoGenerator(), [], new CommonModel(), new CommonInputModel()); + }); + describe('ObjectModel', () => { + test('should render the constrained name as type', () => { + const model = new ConstrainedObjectModel('test', undefined, '', {}); + const type = GoDefaultTypeMapping.Object({constrainedModel: model, renderer}); + expect(type).toEqual(model.name); + }); + }); + describe('Reference', () => { + test('should render the constrained name as type', () => { + const refModel = new ConstrainedAnyModel('test', undefined, ''); + const model = new ConstrainedReferenceModel('test', undefined, '', refModel); + const type = GoDefaultTypeMapping.Reference({constrainedModel: model, renderer}); + expect(type).toEqual(model.name); + }); + }); + describe('Any', () => { + test('should render type', () => { + const model = new ConstrainedAnyModel('test', undefined, ''); + const type = GoDefaultTypeMapping.Any({constrainedModel: model, renderer}); + expect(type).toEqual('interface{}'); + }); + }); + describe('Float', () => { + test('should render type', () => { + const model = new ConstrainedFloatModel('test', undefined, ''); + const type = GoDefaultTypeMapping.Float({constrainedModel: model, renderer}); + expect(type).toEqual('float64'); + }); + }); + describe('Integer', () => { + test('should render type', () => { + const model = new ConstrainedIntegerModel('test', undefined, ''); + const type = GoDefaultTypeMapping.Integer({constrainedModel: model, renderer}); + expect(type).toEqual('int'); + }); + }); + describe('String', () => { + test('should render type', () => { + const model = new ConstrainedStringModel('test', undefined, ''); + const type = GoDefaultTypeMapping.String({constrainedModel: model, renderer}); + expect(type).toEqual('string'); + }); + }); + describe('Boolean', () => { + test('should render type', () => { + const model = new ConstrainedBooleanModel('test', undefined, ''); + const type = GoDefaultTypeMapping.Boolean({constrainedModel: model, renderer}); + expect(type).toEqual('bool'); + }); + }); + + describe('Tuple', () => { + test('should render type', () => { + const model = new ConstrainedTupleModel('test', undefined, '', []); + const type = GoDefaultTypeMapping.Tuple({constrainedModel: model, renderer}); + expect(type).toEqual('[]interface{}'); + }); + }); + + describe('Array', () => { + test('should render type', () => { + const arrayModel = new ConstrainedStringModel('test', undefined, 'string'); + const model = new ConstrainedArrayModel('test', undefined, '', arrayModel); + const type = GoDefaultTypeMapping.Array({constrainedModel: model, renderer}); + expect(type).toEqual('[]string'); + }); + }); + + describe('Enum', () => { + test('should render the constrained name as type', () => { + const model = new ConstrainedEnumModel('test', undefined, '', []); + const type = GoDefaultTypeMapping.Enum({constrainedModel: model, renderer}); + expect(type).toEqual(model.name); + }); + }); + + describe('Union', () => { + test('should render type', () => { + const model = new ConstrainedUnionModel('test', undefined, '', []); + const type = GoDefaultTypeMapping.Union({constrainedModel: model, renderer}); + expect(type).toEqual('interface{}'); + }); + }); + + describe('Dictionary', () => { + test('should render type', () => { + const keyModel = new ConstrainedStringModel('test', undefined, 'string'); + const valueModel = new ConstrainedStringModel('test', undefined, 'string'); + const model = new ConstrainedDictionaryModel('test', undefined, '', keyModel, valueModel); + const type = GoDefaultTypeMapping.Dictionary({constrainedModel: model, renderer}); + expect(type).toEqual('map[string]string'); + }); + }); +}); diff --git a/test/generators/go/constrainer/EnumConstrainer.spec.ts b/test/generators/go/constrainer/EnumConstrainer.spec.ts new file mode 100644 index 0000000000..7d87c73e3a --- /dev/null +++ b/test/generators/go/constrainer/EnumConstrainer.spec.ts @@ -0,0 +1,99 @@ +import { GoDefaultConstraints } from '../../../../src/generators/go/GoConstrainer'; +import {EnumModel} from '../../../../src/models/MetaModel'; +import { ConstrainedEnumModel, ConstrainedEnumValueModel } from '../../../../src'; +import { defaultEnumKeyConstraints, ModelEnumKeyConstraints, DefaultEnumKeyConstraints } from '../../../../src/generators/go/constrainer/EnumConstrainer'; +describe('EnumConstrainer', () => { + const enumModel = new EnumModel('test', undefined, []); + const constrainedEnumModel = new ConstrainedEnumModel('test', undefined, '', []); + + describe('enum keys', () => { + test('should never render special chars', () => { + const constrainedKey = GoDefaultConstraints.enumKey({enumModel, constrainedEnumModel, enumKey: '%'}); + expect(constrainedKey).toEqual('PERCENT'); + }); + test('should not render number as start char', () => { + const constrainedKey = GoDefaultConstraints.enumKey({enumModel, constrainedEnumModel, enumKey: '1'}); + expect(constrainedKey).toEqual('NUMBER_1'); + }); + test('should not contain duplicate keys', () => { + const existingConstrainedEnumValueModel = new ConstrainedEnumValueModel('TEST', 'test'); + const constrainedEnumModel = new ConstrainedEnumModel('test', undefined, '', [existingConstrainedEnumValueModel]); + const constrainedKey = GoDefaultConstraints.enumKey({enumModel, constrainedEnumModel, enumKey: 'TEST'}); + expect(constrainedKey).toEqual('RESERVED_TEST'); + }); + test('should never contain empty keys', () => { + const constrainedKey = GoDefaultConstraints.enumKey({enumModel, constrainedEnumModel, enumKey: ''}); + expect(constrainedKey).toEqual('EMPTY'); + }); + test('should use constant naming format', () => { + const constrainedKey = GoDefaultConstraints.enumKey({enumModel, constrainedEnumModel, enumKey: 'some weird_value!"#2'}); + expect(constrainedKey).toEqual('SOME_SPACE_WEIRD_VALUE_EXCLAMATION_QUOTATION_HASH_2'); + }); + test('should never render reserved keywords', () => { + const constrainedKey = GoDefaultConstraints.enumKey({enumModel, constrainedEnumModel, enumKey: 'return'}); + expect(constrainedKey).toEqual('RESERVED_RETURN'); + }); + }); + describe('enum values', () => { + test('should render string values', () => { + const constrainedValue = GoDefaultConstraints.enumValue({enumModel, constrainedEnumModel, enumValue: 'string value'}); + expect(constrainedValue).toEqual('"string value"'); + }); + test('should render boolean values', () => { + const constrainedValue = GoDefaultConstraints.enumValue({enumModel, constrainedEnumModel, enumValue: true}); + expect(constrainedValue).toEqual('"true"'); + }); + test.skip('should render numbers', () => { + const constrainedValue = GoDefaultConstraints.enumValue({enumModel, constrainedEnumModel, enumValue: 123}); + expect(constrainedValue).toEqual(123); + }); + test.skip('should render object', () => { + const constrainedValue = GoDefaultConstraints.enumValue({enumModel, constrainedEnumModel, enumValue: {test: 'test'}}); + expect(constrainedValue).toEqual('"{\\"test\\":\\"test\\"}"'); + }); + test('should render unknown value', () => { + const constrainedValue = GoDefaultConstraints.enumValue({enumModel, constrainedEnumModel, enumValue: undefined}); + expect(constrainedValue).toEqual('"undefined"'); + }); + }); + describe('custom constraints', () => { + test('should be able to handle undefined', () => { + const constrainFunction = defaultEnumKeyConstraints(undefined); + const value = constrainFunction({enumModel, constrainedEnumModel, enumKey: 'TEST'}); + expect(value).toEqual('TEST'); + }); + test('should be able to overwrite all hooks for enum key', () => { + const mockedConstraintCallbacks: ModelEnumKeyConstraints = { + NAMING_FORMATTER: jest.fn().mockReturnValue(''), + NO_SPECIAL_CHAR: jest.fn().mockReturnValue(''), + NO_NUMBER_START_CHAR: jest.fn().mockReturnValue(''), + NO_DUPLICATE_KEYS: jest.fn().mockReturnValue(''), + NO_EMPTY_VALUE: jest.fn().mockReturnValue(''), + NO_RESERVED_KEYWORDS: jest.fn().mockReturnValue('') + }; + const constrainFunction = defaultEnumKeyConstraints(mockedConstraintCallbacks); + constrainFunction({enumModel, constrainedEnumModel, enumKey: ''}); + //Expect all callbacks to be called + for (const jestMockCallback of Object.values(mockedConstraintCallbacks)) { + expect(jestMockCallback).toHaveBeenCalled(); + } + }); + test('should be able to overwrite one hooks for enum key', () => { + //All but NAMING_FORMATTER, as we customize that + const spies = [ + jest.spyOn(DefaultEnumKeyConstraints, 'NO_SPECIAL_CHAR'), + jest.spyOn(DefaultEnumKeyConstraints, 'NO_NUMBER_START_CHAR'), + jest.spyOn(DefaultEnumKeyConstraints, 'NO_DUPLICATE_KEYS'), + jest.spyOn(DefaultEnumKeyConstraints, 'NO_EMPTY_VALUE'), + jest.spyOn(DefaultEnumKeyConstraints, 'NO_RESERVED_KEYWORDS') + ]; + const jestCallback = jest.fn().mockReturnValue(''); + const constrainFunction = defaultEnumKeyConstraints({NAMING_FORMATTER: jestCallback}); + const constrainedValue = constrainFunction({enumModel, constrainedEnumModel, enumKey: ''}); + expect(constrainedValue).toEqual(''); + for (const jestMockCallback of spies) { + expect(jestMockCallback).toHaveBeenCalled(); + } + }); + }); +}); diff --git a/test/generators/go/constrainer/ModelNameConstrainer.spec.ts b/test/generators/go/constrainer/ModelNameConstrainer.spec.ts new file mode 100644 index 0000000000..3fec4ed14b --- /dev/null +++ b/test/generators/go/constrainer/ModelNameConstrainer.spec.ts @@ -0,0 +1,57 @@ +import { GoDefaultConstraints } from '../../../../src/generators/go/GoConstrainer'; +import { DefaultModelNameConstraints, defaultModelNameConstraints, ModelNameConstraints } from '../../../../src/generators/go/constrainer/ModelNameConstrainer'; +describe('ModelNameConstrainer', () => { + test('should never render special chars', () => { + const constrainedKey = GoDefaultConstraints.modelName({modelName: '%'}); + expect(constrainedKey).toEqual('Percent'); + }); + test('should never render number as start char', () => { + const constrainedKey = GoDefaultConstraints.modelName({modelName: '1'}); + expect(constrainedKey).toEqual('Number_1'); + }); + test('should never contain empty name', () => { + const constrainedKey = GoDefaultConstraints.modelName({modelName: ''}); + expect(constrainedKey).toEqual('Empty'); + }); + test('should use constant naming format', () => { + const constrainedKey = GoDefaultConstraints.modelName({modelName: 'some weird_value!"#2'}); + expect(constrainedKey).toEqual('SomeWeirdValueExclamationQuotationHash_2'); + }); + test('should never render reserved keywords', () => { + const constrainedKey = GoDefaultConstraints.modelName({modelName: 'return'}); + expect(constrainedKey).toEqual('ReservedReturn'); + }); + describe('custom constraints', () => { + test('should be able to overwrite all hooks', () => { + const mockedConstraintCallbacks: ModelNameConstraints = { + NAMING_FORMATTER: jest.fn().mockReturnValue(''), + NO_SPECIAL_CHAR: jest.fn().mockReturnValue(''), + NO_NUMBER_START_CHAR: jest.fn().mockReturnValue(''), + NO_EMPTY_VALUE: jest.fn().mockReturnValue(''), + NO_RESERVED_KEYWORDS: jest.fn().mockReturnValue('') + }; + const constrainFunction = defaultModelNameConstraints(mockedConstraintCallbacks); + constrainFunction({modelName: ''}); + //Expect all callbacks to be called + for (const jestMockCallback of Object.values(mockedConstraintCallbacks)) { + expect(jestMockCallback).toHaveBeenCalled(); + } + }); + test('should be able to overwrite one hooks', () => { + //All but NAMING_FORMATTER, as we customize that + const spies = [ + jest.spyOn(DefaultModelNameConstraints, 'NO_SPECIAL_CHAR'), + jest.spyOn(DefaultModelNameConstraints, 'NO_NUMBER_START_CHAR'), + jest.spyOn(DefaultModelNameConstraints, 'NO_EMPTY_VALUE'), + jest.spyOn(DefaultModelNameConstraints, 'NO_RESERVED_KEYWORDS') + ]; + const jestCallback = jest.fn().mockReturnValue(''); + const constrainFunction = defaultModelNameConstraints({NAMING_FORMATTER: jestCallback}); + const constrainedValue = constrainFunction({modelName: ''}); + expect(constrainedValue).toEqual(''); + for (const jestMockCallback of spies) { + expect(jestMockCallback).toHaveBeenCalled(); + } + }); + }); +}); diff --git a/test/generators/go/constrainer/PropertyKeyConstrainer.spec.ts b/test/generators/go/constrainer/PropertyKeyConstrainer.spec.ts new file mode 100644 index 0000000000..756cdbcbb9 --- /dev/null +++ b/test/generators/go/constrainer/PropertyKeyConstrainer.spec.ts @@ -0,0 +1,70 @@ +import { GoDefaultConstraints } from '../../../../src/generators/go/GoConstrainer'; +import { ConstrainedObjectModel, ObjectModel } from '../../../../src'; +import { DefaultPropertyKeyConstraints, defaultPropertyKeyConstraints, PropertyKeyConstraintOptions } from '../../../../src/generators/go/constrainer/PropertyKeyConstrainer'; +describe('PropertyKeyConstrainer', () => { + const objectModel = new ObjectModel('test', undefined, {}); + const constrainedObjectModel = new ConstrainedObjectModel('test', undefined, '', {}); + + test('should never render special chars', () => { + const constrainedKey = GoDefaultConstraints.propertyKey({constrainedObjectModel, objectModel, propertyKey: '%'}); + expect(constrainedKey).toEqual('Percent'); + }); + test('should not render number as start char', () => { + const constrainedKey = GoDefaultConstraints.propertyKey({constrainedObjectModel, objectModel, propertyKey: '1'}); + expect(constrainedKey).toEqual('Number_1'); + }); + test('should never contain empty name', () => { + const constrainedKey = GoDefaultConstraints.propertyKey({constrainedObjectModel, objectModel, propertyKey: ''}); + expect(constrainedKey).toEqual('Empty'); + }); + test('should use constant naming format', () => { + const constrainedKey = GoDefaultConstraints.propertyKey({constrainedObjectModel, objectModel, propertyKey: 'some weird_value!"#2'}); + expect(constrainedKey).toEqual('SomeWeirdValueExclamationQuotationHash_2'); + }); + test('should not contain duplicate properties', () => { + const objectModel = new ObjectModel('test', undefined, {}); + const propertyModel = new ConstrainedObjectModel('SomeProperty', undefined, '', {}); + const constrainedObjectModel = new ConstrainedObjectModel('test', undefined, '', {SomeProperty: propertyModel}); + const constrainedKey = GoDefaultConstraints.propertyKey({constrainedObjectModel, objectModel, propertyKey: 'SomeProperty'}); + expect(constrainedKey).toEqual('ReservedSomeProperty'); + }); + test('should never render reserved keywords', () => { + const constrainedKey = GoDefaultConstraints.propertyKey({constrainedObjectModel, objectModel, propertyKey: 'return'}); + expect(constrainedKey).toEqual('ReservedReturn'); + }); + describe('custom constraints', () => { + test('should be able to overwrite all hooks', () => { + const mockedConstraintCallbacks: PropertyKeyConstraintOptions = { + NAMING_FORMATTER: jest.fn().mockReturnValue(''), + NO_SPECIAL_CHAR: jest.fn().mockReturnValue(''), + NO_NUMBER_START_CHAR: jest.fn().mockReturnValue(''), + NO_EMPTY_VALUE: jest.fn().mockReturnValue(''), + NO_RESERVED_KEYWORDS: jest.fn().mockReturnValue(''), + NO_DUPLICATE_PROPERTIES: jest.fn().mockReturnValue('') + }; + const constrainFunction = defaultPropertyKeyConstraints(mockedConstraintCallbacks); + constrainFunction({constrainedObjectModel, objectModel, propertyKey: ''}); + //Expect all callbacks to be called + for (const jestMockCallback of Object.values(mockedConstraintCallbacks)) { + expect(jestMockCallback).toHaveBeenCalled(); + } + }); + test('should be able to overwrite one hooks', () => { + //All but NAMING_FORMATTER, as we customize that + const spies = [ + jest.spyOn(DefaultPropertyKeyConstraints, 'NO_SPECIAL_CHAR'), + jest.spyOn(DefaultPropertyKeyConstraints, 'NO_NUMBER_START_CHAR'), + jest.spyOn(DefaultPropertyKeyConstraints, 'NO_EMPTY_VALUE'), + jest.spyOn(DefaultPropertyKeyConstraints, 'NO_RESERVED_KEYWORDS'), + jest.spyOn(DefaultPropertyKeyConstraints, 'NO_DUPLICATE_PROPERTIES') + ]; + const jestCallback = jest.fn().mockReturnValue(''); + const constrainFunction = defaultPropertyKeyConstraints({NAMING_FORMATTER: jestCallback}); + const constrainedValue = constrainFunction({constrainedObjectModel, objectModel, propertyKey: ''}); + expect(constrainedValue).toEqual(''); + for (const jestMockCallback of spies) { + expect(jestMockCallback).toHaveBeenCalled(); + } + }); + }); +});