diff --git a/frontend/language/src/nb.json b/frontend/language/src/nb.json
index ca565af748f..9c5633665ad 100644
--- a/frontend/language/src/nb.json
+++ b/frontend/language/src/nb.json
@@ -1440,6 +1440,7 @@
"ux_editor.component_properties.minNumberOfAttachments": "Minimum antall vedlegg",
"ux_editor.component_properties.mode": "Modus",
"ux_editor.component_properties.openInNewTab": "Lenken skal åpnes i ny fane",
+ "ux_editor.component_properties.optionalIndicator": "Vis valgfri-indikator på ledetekst",
"ux_editor.component_properties.options": "Alternativer",
"ux_editor.component_properties.optionsId": "Kodeliste",
"ux_editor.component_properties.pageBreak": "PDF-innstillinger (pageBreak)",
diff --git a/frontend/packages/ux-editor/src/components/config/FormComponentConfig.test.tsx b/frontend/packages/ux-editor/src/components/config/FormComponentConfig.test.tsx
index 1b7112297e8..8d812199f11 100644
--- a/frontend/packages/ux-editor/src/components/config/FormComponentConfig.test.tsx
+++ b/frontend/packages/ux-editor/src/components/config/FormComponentConfig.test.tsx
@@ -96,6 +96,87 @@ describe('FormComponentConfig', () => {
expect(screen.queryByText('nullProperty')).not.toBeInTheDocument();
});
+ it('should render nothing if schema is undefined', () => {
+ render({
+ props: {
+ schema: undefined,
+ },
+ });
+ expect(
+ screen.queryByText(textMock(`ux_editor.component_properties.grid`)),
+ ).not.toBeInTheDocument();
+ });
+
+ it('should render nothing if schema properties are undefined', () => {
+ render({
+ props: {
+ schema: {
+ properties: undefined,
+ },
+ },
+ });
+ expect(
+ screen.queryByText(textMock(`ux_editor.component_properties.grid`)),
+ ).not.toBeInTheDocument();
+ });
+
+ it('should not render property if it is unsupported', () => {
+ render({
+ props: {
+ schema: {
+ ...InputSchema,
+ properties: {
+ ...InputSchema.properties,
+ unsupportedProperty: {
+ type: 'object',
+ properties: {},
+ additionalProperties: {
+ type: 'string',
+ },
+ },
+ },
+ },
+ },
+ });
+ expect(
+ screen.queryByText(textMock(`ux_editor.component_properties.unsupportedProperty`)),
+ ).not.toBeInTheDocument();
+ });
+
+ it('should only render array properties with items of type string AND enum values', () => {
+ render({
+ props: {
+ schema: {
+ ...InputSchema,
+ properties: {
+ ...InputSchema.properties,
+ supportedArrayProperty: {
+ type: 'array',
+ items: {
+ type: 'string',
+ enum: ['option1', 'option2'],
+ },
+ },
+ unsupportedArrayProperty: {
+ type: 'array',
+ items: {
+ type: 'string',
+ },
+ },
+ },
+ },
+ },
+ });
+ expect(
+ screen.getByRole('combobox', {
+ name: textMock(`ux_editor.component_properties.supportedArrayProperty`),
+ }),
+ ).toBeInTheDocument();
+ expect(
+ screen.queryByLabelText(textMock(`ux_editor.component_properties.unsupportedArrayProperty`)),
+ ).not.toBeInTheDocument();
+ });
+
const render = ({
props = {},
queries = {},
diff --git a/frontend/packages/ux-editor/src/components/config/FormComponentConfig.tsx b/frontend/packages/ux-editor/src/components/config/FormComponentConfig.tsx
index 329b5573fc5..dee2fe7d7e2 100644
--- a/frontend/packages/ux-editor/src/components/config/FormComponentConfig.tsx
+++ b/frontend/packages/ux-editor/src/components/config/FormComponentConfig.tsx
@@ -1,13 +1,14 @@
import React from 'react';
-import { Alert, Heading } from '@digdir/design-system-react';
+import { Alert, Card, Heading, Paragraph } from '@digdir/design-system-react';
import type { FormComponent } from '../../types/FormComponent';
import { EditBooleanValue } from './editModal/EditBooleanValue';
import { EditNumberValue } from './editModal/EditNumberValue';
import { EditStringValue } from './editModal/EditStringValue';
-import { useText } from '../../hooks';
+import { useComponentPropertyLabel, useText } from '../../hooks';
import {
- ExpressionSchemaBooleanDefinitionReference,
- getUnsupportedPropertyTypes,
+ PropertyTypes,
+ propertyKeysToExcludeFromComponentConfig,
+ getSupportedPropertyKeysForPropertyType,
} from '../../utils/component';
import { EditGrid } from './editModal/EditGrid';
import type { FormItem } from '../../types/FormItem';
@@ -22,6 +23,7 @@ export interface FormComponentConfigProps extends IEditFormComponentProps {
schema: any;
hideUnsupported?: boolean;
}
+
export const FormComponentConfig = ({
schema,
editFormId,
@@ -30,26 +32,53 @@ export const FormComponentConfig = ({
hideUnsupported,
}: FormComponentConfigProps) => {
const t = useText();
+ const componentPropertyLabel = useComponentPropertyLabel();
if (!schema?.properties) return null;
- const {
- children,
- dataModelBindings,
- required,
- readOnly,
- id,
- textResourceBindings,
- type,
- options,
- optionsId,
- hasCustomFileEndings,
- validFileEndings,
- grid,
- ...rest
- } = schema.properties;
+ const { properties } = schema;
+ const { hasCustomFileEndings, validFileEndings, grid } = properties;
+
+ // Add any properties that have a custom implementation to this list so they are not duplicated in the generic view
+ const customProperties = ['hasCustomFileEndings', 'validFileEndings', 'grid', 'children'];
+
+ const booleanPropertyKeys: string[] = getSupportedPropertyKeysForPropertyType(
+ schema.properties,
+ [PropertyTypes.boolean],
+ customProperties,
+ );
+ const stringPropertyKeys: string[] = getSupportedPropertyKeysForPropertyType(
+ schema.properties,
+ [PropertyTypes.string],
+ customProperties,
+ );
+ const numberPropertyKeys: string[] = getSupportedPropertyKeysForPropertyType(
+ schema.properties,
+ [PropertyTypes.number, PropertyTypes.integer],
+ customProperties,
+ );
+ const arrayPropertyKeys: string[] = getSupportedPropertyKeysForPropertyType(
+ schema.properties,
+ [PropertyTypes.array],
+ customProperties,
+ );
+ const objectPropertyKeys: string[] = getSupportedPropertyKeysForPropertyType(
+ schema.properties,
+ [PropertyTypes.object],
+ [...customProperties, 'source'],
+ );
- const unsupportedPropertyKeys: string[] = getUnsupportedPropertyTypes(rest);
+ const unsupportedPropertyKeys: string[] = Object.keys(properties).filter((key) => {
+ return (
+ !booleanPropertyKeys.includes(key) &&
+ !stringPropertyKeys.includes(key) &&
+ !numberPropertyKeys.includes(key) &&
+ !arrayPropertyKeys.includes(key) &&
+ !objectPropertyKeys.includes(key) &&
+ !customProperties.includes(key) &&
+ !propertyKeysToExcludeFromComponentConfig.includes(key)
+ );
+ });
return (
<>
@@ -71,6 +100,20 @@ export const FormComponentConfig = ({
)}
+ {/** Boolean fields, incl. expression type */}
+ {booleanPropertyKeys.map((propertyKey) => {
+ return (
+
+ );
+ })}
+
+ {/** Custom logic for custom file endings */}
{hasCustomFileEndings && (
<>
)}
- {readOnly && (
-
- )}
- {required && (
-
- )}
+ {/** String properties */}
+ {stringPropertyKeys.map((propertyKey) => {
+ return (
+
+ );
+ })}
- {Object.keys(rest).map((propertyKey) => {
- if (!rest[propertyKey]) return null;
- if (
- rest[propertyKey].type === 'boolean' ||
- rest[propertyKey].$ref?.endsWith(ExpressionSchemaBooleanDefinitionReference)
- ) {
- return (
-
- );
- }
- if (rest[propertyKey].type === 'number' || rest[propertyKey].type === 'integer') {
- return (
-
- );
- }
- if (rest[propertyKey].type === 'string') {
- return (
-
- );
- }
- if (rest[propertyKey].type === 'array' && rest[propertyKey].items?.type === 'string') {
- return (
- {
+ return (
+
+ );
+ })}
+
+ {/** Array properties with enum values) */}
+ {arrayPropertyKeys.map((propertyKey) => {
+ return (
+
+ );
+ })}
+
+ {/** Object properties */}
+ {objectPropertyKeys.map((propertyKey) => {
+ return (
+
+
+ {componentPropertyLabel(propertyKey)}
+
+ {properties[propertyKey]?.description && (
+ {properties[propertyKey].description}
+ )}
+ {
+ handleComponentUpdate({
+ ...component,
+ [propertyKey]: updatedComponent,
+ });
+ }}
+ editFormId={editFormId}
+ hideUnsupported
/>
- );
- }
- return null;
+
+ );
})}
{/* Show information about unsupported properties if there are any */}
{unsupportedPropertyKeys.length > 0 && !hideUnsupported && (
diff --git a/frontend/packages/ux-editor/src/hooks/index.ts b/frontend/packages/ux-editor/src/hooks/index.ts
index 6e3184f1882..e0ca80c4929 100644
--- a/frontend/packages/ux-editor/src/hooks/index.ts
+++ b/frontend/packages/ux-editor/src/hooks/index.ts
@@ -9,3 +9,4 @@ export { useText } from './useText';
export { useTextResourcesSelector } from './useTextResourcesSelector';
export type { ComponentValidationResult, ErrorCode } from './useValidateComponent';
export { useValidateComponent } from './useValidateComponent';
+export { useComponentPropertyLabel } from './useComponentPropertyLabel';
diff --git a/frontend/packages/ux-editor/src/testing/schemas/json/component/Address.schema.v1.json b/frontend/packages/ux-editor/src/testing/schemas/json/component/Address.schema.v1.json
index f5b63dc9724..f044bad4956 100644
--- a/frontend/packages/ux-editor/src/testing/schemas/json/component/Address.schema.v1.json
+++ b/frontend/packages/ux-editor/src/testing/schemas/json/component/Address.schema.v1.json
@@ -72,7 +72,8 @@
"Required",
"AllExceptRequired",
"All"
- ]
+ ],
+ "type": "string"
}
},
"renderAsSummary": {
diff --git a/frontend/packages/ux-editor/src/testing/schemas/json/component/Checkboxes.schema.v1.json b/frontend/packages/ux-editor/src/testing/schemas/json/component/Checkboxes.schema.v1.json
index 1eb0fb17ae6..1b42a5d2716 100644
--- a/frontend/packages/ux-editor/src/testing/schemas/json/component/Checkboxes.schema.v1.json
+++ b/frontend/packages/ux-editor/src/testing/schemas/json/component/Checkboxes.schema.v1.json
@@ -72,7 +72,8 @@
"Required",
"AllExceptRequired",
"All"
- ]
+ ],
+ "type": "string"
}
},
"renderAsSummary": {
diff --git a/frontend/packages/ux-editor/src/testing/schemas/json/component/Custom.schema.v1.json b/frontend/packages/ux-editor/src/testing/schemas/json/component/Custom.schema.v1.json
index 1fc24d2823a..0645193d91f 100644
--- a/frontend/packages/ux-editor/src/testing/schemas/json/component/Custom.schema.v1.json
+++ b/frontend/packages/ux-editor/src/testing/schemas/json/component/Custom.schema.v1.json
@@ -72,7 +72,8 @@
"Required",
"AllExceptRequired",
"All"
- ]
+ ],
+ "type": "string"
}
},
"renderAsSummary": {
diff --git a/frontend/packages/ux-editor/src/testing/schemas/json/component/Datepicker.schema.v1.json b/frontend/packages/ux-editor/src/testing/schemas/json/component/Datepicker.schema.v1.json
index 74650b91972..a77cf3bd0cb 100644
--- a/frontend/packages/ux-editor/src/testing/schemas/json/component/Datepicker.schema.v1.json
+++ b/frontend/packages/ux-editor/src/testing/schemas/json/component/Datepicker.schema.v1.json
@@ -72,7 +72,8 @@
"Required",
"AllExceptRequired",
"All"
- ]
+ ],
+ "type": "string"
}
},
"renderAsSummary": {
diff --git a/frontend/packages/ux-editor/src/testing/schemas/json/component/Dropdown.schema.v1.json b/frontend/packages/ux-editor/src/testing/schemas/json/component/Dropdown.schema.v1.json
index bec256a7946..eccd8e5c282 100644
--- a/frontend/packages/ux-editor/src/testing/schemas/json/component/Dropdown.schema.v1.json
+++ b/frontend/packages/ux-editor/src/testing/schemas/json/component/Dropdown.schema.v1.json
@@ -72,7 +72,8 @@
"Required",
"AllExceptRequired",
"All"
- ]
+ ],
+ "type": "string"
}
},
"renderAsSummary": {
diff --git a/frontend/packages/ux-editor/src/testing/schemas/json/component/FileUpload.schema.v1.json b/frontend/packages/ux-editor/src/testing/schemas/json/component/FileUpload.schema.v1.json
index 2ab4af18e4f..d4ddf60e01f 100644
--- a/frontend/packages/ux-editor/src/testing/schemas/json/component/FileUpload.schema.v1.json
+++ b/frontend/packages/ux-editor/src/testing/schemas/json/component/FileUpload.schema.v1.json
@@ -72,7 +72,8 @@
"Required",
"AllExceptRequired",
"All"
- ]
+ ],
+ "type": "string"
}
},
"renderAsSummary": {
diff --git a/frontend/packages/ux-editor/src/testing/schemas/json/component/FileUploadWithTag.schema.v1.json b/frontend/packages/ux-editor/src/testing/schemas/json/component/FileUploadWithTag.schema.v1.json
index 37981018c93..31be3d04567 100644
--- a/frontend/packages/ux-editor/src/testing/schemas/json/component/FileUploadWithTag.schema.v1.json
+++ b/frontend/packages/ux-editor/src/testing/schemas/json/component/FileUploadWithTag.schema.v1.json
@@ -72,7 +72,8 @@
"Required",
"AllExceptRequired",
"All"
- ]
+ ],
+ "type": "string"
}
},
"renderAsSummary": {
diff --git a/frontend/packages/ux-editor/src/testing/schemas/json/component/Input.schema.v1.json b/frontend/packages/ux-editor/src/testing/schemas/json/component/Input.schema.v1.json
index 6e4fd54acfa..45b0aa78a64 100644
--- a/frontend/packages/ux-editor/src/testing/schemas/json/component/Input.schema.v1.json
+++ b/frontend/packages/ux-editor/src/testing/schemas/json/component/Input.schema.v1.json
@@ -72,7 +72,8 @@
"Required",
"AllExceptRequired",
"All"
- ]
+ ],
+ "type": "string"
}
},
"renderAsSummary": {
@@ -170,6 +171,7 @@
"currency": {
"title": "Language-sensitive currency formatting",
"description": "Enables currency to be language sensitive based on selected app language. Note: parts that already exist in number property are not overridden by this prop.",
+ "type": "string",
"enum": [
"AED",
"AFN",
diff --git a/frontend/packages/ux-editor/src/testing/schemas/json/component/Likert.schema.v1.json b/frontend/packages/ux-editor/src/testing/schemas/json/component/Likert.schema.v1.json
index de171823129..c3b49abf956 100644
--- a/frontend/packages/ux-editor/src/testing/schemas/json/component/Likert.schema.v1.json
+++ b/frontend/packages/ux-editor/src/testing/schemas/json/component/Likert.schema.v1.json
@@ -72,7 +72,8 @@
"Required",
"AllExceptRequired",
"All"
- ]
+ ],
+ "type": "string"
}
},
"renderAsSummary": {
diff --git a/frontend/packages/ux-editor/src/testing/schemas/json/component/LikertItem.schema.v1.json b/frontend/packages/ux-editor/src/testing/schemas/json/component/LikertItem.schema.v1.json
index 4d40e589715..874eb7e94db 100644
--- a/frontend/packages/ux-editor/src/testing/schemas/json/component/LikertItem.schema.v1.json
+++ b/frontend/packages/ux-editor/src/testing/schemas/json/component/LikertItem.schema.v1.json
@@ -72,7 +72,8 @@
"Required",
"AllExceptRequired",
"All"
- ]
+ ],
+ "type": "string"
}
},
"renderAsSummary": {
diff --git a/frontend/packages/ux-editor/src/testing/schemas/json/component/List.schema.v1.json b/frontend/packages/ux-editor/src/testing/schemas/json/component/List.schema.v1.json
index a20b22eac7d..836bc54b2d7 100644
--- a/frontend/packages/ux-editor/src/testing/schemas/json/component/List.schema.v1.json
+++ b/frontend/packages/ux-editor/src/testing/schemas/json/component/List.schema.v1.json
@@ -72,7 +72,8 @@
"Required",
"AllExceptRequired",
"All"
- ]
+ ],
+ "type": "string"
}
},
"renderAsSummary": {
diff --git a/frontend/packages/ux-editor/src/testing/schemas/json/component/Map.schema.v1.json b/frontend/packages/ux-editor/src/testing/schemas/json/component/Map.schema.v1.json
index 411ae99872c..1b42456f7e0 100644
--- a/frontend/packages/ux-editor/src/testing/schemas/json/component/Map.schema.v1.json
+++ b/frontend/packages/ux-editor/src/testing/schemas/json/component/Map.schema.v1.json
@@ -72,7 +72,8 @@
"Required",
"AllExceptRequired",
"All"
- ]
+ ],
+ "type": "string"
}
},
"renderAsSummary": {
diff --git a/frontend/packages/ux-editor/src/testing/schemas/json/component/MultipleSelect.schema.v1.json b/frontend/packages/ux-editor/src/testing/schemas/json/component/MultipleSelect.schema.v1.json
index 71ab6cfcbff..4c66ce2e2f0 100644
--- a/frontend/packages/ux-editor/src/testing/schemas/json/component/MultipleSelect.schema.v1.json
+++ b/frontend/packages/ux-editor/src/testing/schemas/json/component/MultipleSelect.schema.v1.json
@@ -72,7 +72,8 @@
"Required",
"AllExceptRequired",
"All"
- ]
+ ],
+ "type": "string"
}
},
"renderAsSummary": {
diff --git a/frontend/packages/ux-editor/src/testing/schemas/json/component/RadioButtons.schema.v1.json b/frontend/packages/ux-editor/src/testing/schemas/json/component/RadioButtons.schema.v1.json
index 2cb3d268ceb..86acb101d06 100644
--- a/frontend/packages/ux-editor/src/testing/schemas/json/component/RadioButtons.schema.v1.json
+++ b/frontend/packages/ux-editor/src/testing/schemas/json/component/RadioButtons.schema.v1.json
@@ -72,7 +72,8 @@
"Required",
"AllExceptRequired",
"All"
- ]
+ ],
+ "type": "string"
}
},
"renderAsSummary": {
diff --git a/frontend/packages/ux-editor/src/testing/schemas/json/component/RepeatingGroup.schema.v1.json b/frontend/packages/ux-editor/src/testing/schemas/json/component/RepeatingGroup.schema.v1.json
index 2a968b95a47..e8a9bc23ca5 100644
--- a/frontend/packages/ux-editor/src/testing/schemas/json/component/RepeatingGroup.schema.v1.json
+++ b/frontend/packages/ux-editor/src/testing/schemas/json/component/RepeatingGroup.schema.v1.json
@@ -140,7 +140,8 @@
"Required",
"AllExceptRequired",
"All"
- ]
+ ],
+ "type": "string"
}
},
"edit": {
diff --git a/frontend/packages/ux-editor/src/testing/schemas/json/component/TextArea.schema.v1.json b/frontend/packages/ux-editor/src/testing/schemas/json/component/TextArea.schema.v1.json
index 3ecda35cb04..644605bc6c8 100644
--- a/frontend/packages/ux-editor/src/testing/schemas/json/component/TextArea.schema.v1.json
+++ b/frontend/packages/ux-editor/src/testing/schemas/json/component/TextArea.schema.v1.json
@@ -72,7 +72,8 @@
"Required",
"AllExceptRequired",
"All"
- ]
+ ],
+ "type": "string"
}
},
"renderAsSummary": {
diff --git a/frontend/packages/ux-editor/src/utils/component.test.ts b/frontend/packages/ux-editor/src/utils/component.test.ts
index 6b235109ab1..8bf2c65bea5 100644
--- a/frontend/packages/ux-editor/src/utils/component.test.ts
+++ b/frontend/packages/ux-editor/src/utils/component.test.ts
@@ -3,11 +3,14 @@ import {
addOptionToComponent,
changeComponentOptionLabel,
changeTextResourceBinding,
- ExpressionSchemaBooleanDefinitionReference,
+ getExpressionSchemaDefinitionReference,
generateFormItem,
- getUnsupportedPropertyTypes,
isPropertyTypeSupported,
setComponentProperty,
+ EXPRESSION_SCHEMA_BASE_DEFINITION_REFERENCE,
+ PropertyTypes,
+ propertyTypeMatcher,
+ getSupportedPropertyKeysForPropertyType,
} from './component';
import { ComponentType } from 'app-shared/types/ComponentType';
import type {
@@ -200,113 +203,233 @@ describe('Component utils', () => {
});
});
- describe('getUnsupportedPropertyTypes', () => {
- it('Returns empty array when only properties are provided', () => {
- const properties = {
- testProperty1: {
- type: 'string',
- },
- testProperty2: {
- type: 'number',
- },
- testProperty3: {
- type: 'array',
- items: {
- type: 'string',
- },
- },
- testProperty4: {
- $ref: ExpressionSchemaBooleanDefinitionReference,
- },
- testProperty5: {
- type: 'integer',
- },
- testProperty6: {
- type: 'object',
- },
- testProperty7: {
- type: 'boolean',
- },
- };
- expect(getUnsupportedPropertyTypes(properties)).toEqual(['testProperty6']);
+ describe('isPropertyTypeSupported', () => {
+ it('should return true if property type is supported', () => {
+ [
+ PropertyTypes.boolean,
+ PropertyTypes.number,
+ PropertyTypes.integer,
+ PropertyTypes.string,
+ PropertyTypes.object,
+ PropertyTypes.array,
+ ].forEach((type) => {
+ expect(
+ isPropertyTypeSupported({
+ type,
+ }),
+ ).toBe(true);
+ });
});
- it('Returns empty array when no properties are provided', () => {
- const properties = {};
- expect(getUnsupportedPropertyTypes(properties)).toEqual([]);
+
+ it('should return true if property ref is supported', () => {
+ [
+ PropertyTypes.boolean,
+ PropertyTypes.number,
+ PropertyTypes.integer,
+ PropertyTypes.string,
+ ].forEach((type) => {
+ expect(
+ isPropertyTypeSupported({
+ $ref: getExpressionSchemaDefinitionReference(type),
+ }),
+ ).toBe(true);
+ });
});
- it('Returns array of unsupported property keys when known unsupported property keys are provided', () => {
- const properties = {
- children: 'testValue',
- };
- expect(getUnsupportedPropertyTypes(properties, ['children'])).toEqual(['children']);
+
+ it('should return false if property ref is not supported', () => {
+ [PropertyTypes.object, PropertyTypes.array].forEach((type) => {
+ expect(
+ isPropertyTypeSupported({
+ $ref: getExpressionSchemaDefinitionReference(type),
+ }),
+ ).toBe(false);
+ });
});
- it('Returns array of unsupported property keys when unsupported property keys are given', () => {
- const properties = {
- testProperty1: {
- $ref: 'testRef',
- },
- testProperty2: {
- type: 'array',
- items: {
- type: 'object',
- },
- },
- testProperty3: {
- type: 'string',
- },
- };
- const result = getUnsupportedPropertyTypes(properties);
- expect(result).toEqual(['testProperty1', 'testProperty2']);
+
+ it('should return false if property ref is not supported', () => {
+ expect(
+ isPropertyTypeSupported({
+ $ref: 'test',
+ }),
+ ).toBe(false);
});
- });
- describe('isPropertyTypeSupported', () => {
- it('should return true if property type is supported', () => {
+ it('should return true if property type is supported and propertyKey is undefined', () => {
expect(
isPropertyTypeSupported({
type: 'string',
}),
).toBe(true);
});
+ });
+
+ describe('getExpressionSchemaDefinitionReference', () => {
+ it('should return correct reference for given type', () => {
+ expect(getExpressionSchemaDefinitionReference(PropertyTypes.array)).toBe(
+ `${EXPRESSION_SCHEMA_BASE_DEFINITION_REFERENCE}array`,
+ );
+ });
+ });
- it('should return true if property ref is supported', () => {
+ describe('propertyTypeMatcher', () => {
+ it('should return false if property does not exist', () => {
+ expect(propertyTypeMatcher(undefined, PropertyTypes.string)).toBe(false);
+ });
+
+ it('should return true if property type matches', () => {
+ expect(propertyTypeMatcher({ type: 'string' }, PropertyTypes.string)).toBe(true);
+ });
+
+ it('should return false if property type does not match', () => {
+ expect(propertyTypeMatcher({ type: 'number' }, PropertyTypes.string)).toBe(false);
+ });
+
+ it('should return true if property has a supported ref', () => {
expect(
- isPropertyTypeSupported({
- $ref: ExpressionSchemaBooleanDefinitionReference,
- }),
+ propertyTypeMatcher(
+ {
+ $ref: getExpressionSchemaDefinitionReference(PropertyTypes.string),
+ },
+ PropertyTypes.string,
+ ),
).toBe(true);
});
- it('should return true for property of array type with items that are type string', () => {
+
+ it('should return true for a property of string type with enum even if type: string is not defined explicitly', () => {
expect(
- isPropertyTypeSupported({
- type: 'array',
- items: {
- type: 'string',
+ propertyTypeMatcher(
+ {
+ enum: ['test'],
},
- }),
+ PropertyTypes.string,
+ ),
).toBe(true);
});
- it('should return false for property type object', () => {
+
+ it('should return false for a property with no type defined and no enum defined', () => {
+ expect(propertyTypeMatcher({ something: 'test' }, PropertyTypes.string)).toBe(false);
+ });
+
+ it('should return true for a property of array type with items that have enum value', () => {
expect(
- isPropertyTypeSupported({
- type: 'object',
- }),
+ propertyTypeMatcher(
+ {
+ type: 'array',
+ items: {
+ enum: ['test'],
+ },
+ },
+ PropertyTypes.array,
+ ),
+ ).toBe(true);
+ });
+
+ it('should return false for a property of array type with items that have no enum value', () => {
+ expect(
+ propertyTypeMatcher(
+ {
+ type: 'array',
+ items: {
+ type: 'string',
+ },
+ },
+ PropertyTypes.array,
+ ),
).toBe(false);
});
- it('should return false if property ref is not supported', () => {
+
+ it('should return false for a property of array type with no items defined', () => {
+ expect(propertyTypeMatcher({ type: 'array' }, PropertyTypes.array)).toBe(false);
+ });
+
+ it('should return false for a property of object type with no properties defined', () => {
+ expect(propertyTypeMatcher({ type: 'object' }, PropertyTypes.object)).toBe(false);
+ });
+
+ it('should return false for a property of object type with additionalProperties defined', () => {
expect(
- isPropertyTypeSupported({
- $ref: 'test',
- }),
+ propertyTypeMatcher(
+ {
+ type: 'object',
+ properties: {},
+ additionalProperties: {
+ type: 'string',
+ },
+ },
+ PropertyTypes.object,
+ ),
).toBe(false);
});
- it('should return true if property type is supported and propertyKey is undefined', () => {
+ it('should return true for a property of object type with defined properties and no additionalProperties', () => {
expect(
- isPropertyTypeSupported({
- type: 'string',
- }),
+ propertyTypeMatcher(
+ {
+ type: 'object',
+ properties: {
+ testProperty: {
+ type: 'string',
+ },
+ },
+ },
+ PropertyTypes.object,
+ ),
).toBe(true);
});
});
+
+ describe('getSupportedPropertyKeysForPropertyType', () => {
+ it('should return empty array if no properties are provided', () => {
+ expect(getSupportedPropertyKeysForPropertyType({}, [PropertyTypes.string])).toEqual([]);
+ });
+
+ it('should return empty array if no property keys are of the expected property types', () => {
+ expect(
+ getSupportedPropertyKeysForPropertyType(
+ {
+ testProperty: {
+ type: 'number',
+ },
+ },
+ [PropertyTypes.string],
+ ),
+ ).toEqual([]);
+ });
+
+ it('should return array of property keys of the expected property types', () => {
+ expect(
+ getSupportedPropertyKeysForPropertyType(
+ {
+ testProperty: {
+ type: 'string',
+ },
+ testProperty2: {
+ type: 'number',
+ },
+ },
+ [PropertyTypes.string],
+ ),
+ ).toEqual(['testProperty']);
+ });
+
+ it('should only return property keys that are not in the excludeKeys array', () => {
+ expect(
+ getSupportedPropertyKeysForPropertyType(
+ {
+ testProperty: {
+ type: 'string',
+ },
+ testProperty1: {
+ type: 'string',
+ },
+ testProperty2: {
+ type: 'number',
+ },
+ },
+ [PropertyTypes.string],
+ ['testProperty'],
+ ),
+ ).toEqual(['testProperty1']);
+ });
+ });
});
diff --git a/frontend/packages/ux-editor/src/utils/component.ts b/frontend/packages/ux-editor/src/utils/component.ts
index 1f4d755acf6..8b057346628 100644
--- a/frontend/packages/ux-editor/src/utils/component.ts
+++ b/frontend/packages/ux-editor/src/utils/component.ts
@@ -42,6 +42,25 @@ export enum AddressKeys {
houseNumber = 'houseNumber',
}
+export enum PropertyTypes {
+ boolean = 'boolean',
+ number = 'number',
+ integer = 'integer',
+ string = 'string',
+ object = 'object',
+ array = 'array',
+}
+
+// Add any properties that are rendered elsewhere to this list so they are not duplicated in the generic view
+export const propertyKeysToExcludeFromComponentConfig = [
+ 'id',
+ 'type',
+ 'dataModelBindings',
+ 'textResourceBindings',
+ 'options',
+ 'optionsId',
+];
+
export const changeTextResourceBinding = (
component: FormComponent,
bindingKey: string,
@@ -54,6 +73,60 @@ export const changeTextResourceBinding = (
},
});
+/**
+ * Function that returns true if the given property matches the required
+ * conditions for the provided property type.
+ * @param propertyKey The key of the property to check.
+ * @param propertyType The expected property type to check for.
+ * @param properties The properties to check.
+ * @returns
+ */
+export const propertyTypeMatcher = (property: KeyValuePairs, propertyType: PropertyTypes) => {
+ if (!property) return false;
+ const baseMatch =
+ property.type === propertyType ||
+ property.$ref?.endsWith(getExpressionSchemaDefinitionReference(propertyType));
+
+ switch (propertyType) {
+ case PropertyTypes.string:
+ // Not all schemas with enum value explicitly specifies type as string
+ return baseMatch || !!property.enum;
+ case PropertyTypes.array:
+ // Currently only supporting array of strings with specified enum values
+ return baseMatch && !!property.items?.enum;
+ case PropertyTypes.object:
+ // Currently only supporting object with specifiec properties and no additional properties
+ return baseMatch && !!property.properties && !property.additionalProperties;
+ default:
+ return baseMatch;
+ }
+};
+
+/**
+ * Function that returns an array of supported property keys for the given property type(s).
+ * @param properties The properties to check.
+ * @param propertyTypes The expected property types to check for.
+ * @param excludeKeys Property keys that should be excluded from the result.
+ * @returns An array of supported property keys.
+ */
+export const getSupportedPropertyKeysForPropertyType = (
+ properties: KeyValuePairs,
+ propertyTypes: PropertyTypes[],
+ excludeKeys: string[] = [],
+) => {
+ return Object.keys(properties).filter((key) => {
+ if (
+ !properties[key] ||
+ !isPropertyTypeSupported(properties[key]) ||
+ excludeKeys.includes(key) ||
+ propertyKeysToExcludeFromComponentConfig.includes(key)
+ )
+ return false;
+
+ return propertyTypes.find((propertyType) => propertyTypeMatcher(properties[key], propertyType));
+ });
+};
+
export const changeTitleBinding = (component: FormComponent, resourceKey: string): FormComponent =>
changeTextResourceBinding(component, 'title', resourceKey);
@@ -116,37 +189,24 @@ export const setComponentProperty = <
[propertyKey]: value,
});
-export const ExpressionSchemaBooleanDefinitionReference =
- 'expression.schema.v1.json#/definitions/boolean';
+export const EXPRESSION_SCHEMA_BASE_DEFINITION_REFERENCE =
+ 'expression.schema.v1.json#/definitions/' as const;
-/**
- * Gets an array of unsupported property keys
- * @param properties The properties object to check.
- * @param knownUnsupportedPropertyKeys An array of additional known unsupported property keys.
- * @returns An array of unsupported property keys.
- */
-export const getUnsupportedPropertyTypes = (
- properties: KeyValuePairs,
- knownUnsupportedPropertyKeys?: string[],
-) => {
- const propertyKeys = Object.keys(properties);
- let unsupportedPropertyKeys = propertyKeys
- .filter((key) =>
- knownUnsupportedPropertyKeys ? !knownUnsupportedPropertyKeys.includes(key) : true,
- )
- .filter((key) => {
- return !isPropertyTypeSupported(properties[key]);
- });
-
- if (knownUnsupportedPropertyKeys) {
- unsupportedPropertyKeys = unsupportedPropertyKeys.concat(knownUnsupportedPropertyKeys);
- }
-
- return unsupportedPropertyKeys;
+export const getExpressionSchemaDefinitionReference = (type: PropertyTypes) => {
+ return `${EXPRESSION_SCHEMA_BASE_DEFINITION_REFERENCE}${type}`;
};
-const supportedPropertyTypes = ['boolean', 'number', 'integer', 'string'];
-const supportedPropertyRefs = [ExpressionSchemaBooleanDefinitionReference];
+const supportedPropertyTypes = [
+ PropertyTypes.boolean,
+ PropertyTypes.number,
+ PropertyTypes.integer,
+ PropertyTypes.string,
+ PropertyTypes.object,
+ PropertyTypes.array,
+];
+const supportedPropertyRefs = supportedPropertyTypes
+ .filter((p) => p !== PropertyTypes.object && p !== PropertyTypes.array)
+ .map((type) => getExpressionSchemaDefinitionReference(type));
/**
* Checks if a given property with optional property key is supported by component config view.
@@ -157,8 +217,6 @@ export const isPropertyTypeSupported = (property: KeyValuePairs) => {
if (property?.$ref) {
return supportedPropertyRefs.includes(property.$ref);
}
- if (property?.type === 'array' && property?.items?.type === 'string') {
- return true;
- }
+
return supportedPropertyTypes.includes(property?.type);
};
diff --git a/frontend/scripts/componentSchemas/schemaUtils.ts b/frontend/scripts/componentSchemas/schemaUtils.ts
index e07d6863472..f3fde6b083b 100644
--- a/frontend/scripts/componentSchemas/schemaUtils.ts
+++ b/frontend/scripts/componentSchemas/schemaUtils.ts
@@ -68,6 +68,8 @@ export const expandRef = (ref: string, layoutSchema: any) => {
export const ensureStringTypeWithEnums = (schema: any) => {
if (schema.enum) {
schema.type = 'string';
+ } else if (schema.items?.enum) {
+ schema.items.type = 'string';
}
};