Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chore/12153 component schema v4 update #12351

Merged
merged 13 commits into from
Feb 22, 2024
Prev Previous commit
Next Next commit
update checks for expression refs
nkylstad committed Feb 20, 2024
commit e5cf211e42dd8afc1f3034082389e101852b213f
1 change: 1 addition & 0 deletions frontend/language/src/nb.json
Original file line number Diff line number Diff line change
@@ -1435,6 +1435,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)",
Original file line number Diff line number Diff line change
@@ -3,14 +3,13 @@ import type { EditSettings, IGenericEditComponent } from './componentConfig';
import { configComponents } from './componentConfig';
import { componentSpecificEditConfig } from './componentConfig';
import { ComponentSpecificContent } from './componentSpecificContent';
import { Switch, Fieldset, Heading } from '@digdir/design-system-react';
import { Switch, Fieldset } from '@digdir/design-system-react';
import classes from './EditFormComponent.module.css';
import { selectedLayoutNameSelector } from '../../selectors/formLayoutSelectors';
import { useComponentSchemaQuery } from '../../hooks/queries/useComponentSchemaQuery';
import { StudioSpinner } from '@studio/components';
import { FormComponentConfig } from './FormComponentConfig';
import { useSelector } from 'react-redux';
import { getComponentTitleByComponentType } from '../../utils/language';
import { useTranslation } from 'react-i18next';
import {
addFeatureFlagToLocalStorage,
@@ -92,9 +91,6 @@ export const EditFormComponent = ({
</Switch>
)}
/>
<Heading level={2} size='xsmall'>
{getComponentTitleByComponentType(component.type, t)} ({component.type})
</Heading>
{showComponentConfigBeta && isPending && <StudioSpinner spinnerText={t('general.loading')} />}
{showComponentConfigBeta && !isPending && (
<>
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ import { EditNumberValue } from './editModal/EditNumberValue';
import { EditStringValue } from './editModal/EditStringValue';
import { useComponentPropertyLabel, useText } from '../../hooks';
import {
ExpressionSchemaBooleanDefinitionReference,
getExpressionSchemaDefinitionReference,
getUnsupportedPropertyTypes,
} from '../../utils/component';
import { EditGrid } from './editModal/EditGrid';
@@ -62,7 +62,7 @@ export const FormComponentConfig = ({
const booleanRestPropertyKeys = getFilteredPropertyKeys((propertyKey) => {
return (
rest[propertyKey].type === 'boolean' ||
rest[propertyKey].$ref?.endsWith(ExpressionSchemaBooleanDefinitionReference)
rest[propertyKey].$ref?.endsWith(getExpressionSchemaDefinitionReference('boolean'))
);
}, rest);

@@ -143,6 +143,7 @@ export const FormComponentConfig = ({
/>
)}
{booleanRestPropertyKeys.map((propertyKey) => {
if (unsupportedPropertyKeys.includes(propertyKey)) return null;
return (
<EditBooleanValue
component={component}
@@ -156,11 +157,12 @@ export const FormComponentConfig = ({

{/** String and number fields (incl. arrays with enum values) */}
{remainingRestPropertyKeys.map((propertyKey) => {
if (unsupportedPropertyKeys.includes(propertyKey)) return null;
if (!rest[propertyKey]) return null;
if (
rest[propertyKey].type === 'number' ||
rest[propertyKey].type === 'integer' ||
rest[propertyKey].$ref?.endsWith('expression.schema.v1.json#/definitions/number')
rest[propertyKey].$ref?.endsWith(getExpressionSchemaDefinitionReference('number'))
) {
return (
<EditNumberValue
@@ -175,7 +177,7 @@ export const FormComponentConfig = ({
if (
rest[propertyKey].type === 'string' ||
rest[propertyKey].enum ||
rest[propertyKey].$ref?.endsWith('expression.schema.v1.json#/definitions/string')
rest[propertyKey].$ref?.endsWith(getExpressionSchemaDefinitionReference('string'))
) {
return (
<EditStringValue
@@ -208,6 +210,7 @@ export const FormComponentConfig = ({

{/** Object properties */}
{objectRestPropertyKeys.map((propertyKey) => {
if (unsupportedPropertyKeys.includes(propertyKey)) return null;
if (rest[propertyKey].type === 'object' && rest[propertyKey].properties) {
standeren marked this conversation as resolved.
Show resolved Hide resolved
return (
<Card key={propertyKey}>
67 changes: 56 additions & 11 deletions frontend/packages/ux-editor/src/utils/component.test.ts
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ import {
addOptionToComponent,
changeComponentOptionLabel,
changeTextResourceBinding,
ExpressionSchemaBooleanDefinitionReference,
getExpressionSchemaDefinitionReference,
generateFormItem,
getUnsupportedPropertyTypes,
isPropertyTypeSupported,
@@ -201,7 +201,7 @@ describe('Component utils', () => {
});

describe('getUnsupportedPropertyTypes', () => {
it('Returns empty array when only properties are provided', () => {
it('Returns empty array when only valid properties are provided', () => {
const properties = {
testProperty1: {
type: 'string',
@@ -216,19 +216,24 @@ describe('Component utils', () => {
},
},
testProperty4: {
$ref: ExpressionSchemaBooleanDefinitionReference,
$ref: getExpressionSchemaDefinitionReference('boolean'),
},
testProperty5: {
type: 'integer',
},
testProperty6: {
type: 'object',
properties: {
testProperty6_1: {
type: 'string',
},
},
},
testProperty7: {
type: 'boolean',
},
};
expect(getUnsupportedPropertyTypes(properties)).toEqual(['testProperty6']);
expect(getUnsupportedPropertyTypes(properties)).toEqual([]);
});
it('Returns empty array when no properties are provided', () => {
const properties = {};
@@ -237,8 +242,18 @@ describe('Component utils', () => {
it('Returns array of unsupported property keys when known unsupported property keys are provided', () => {
const properties = {
children: 'testValue',
testProperty1: {
type: 'object',
properties: {},
additionalProperties: {
type: 'string',
},
},
};
expect(getUnsupportedPropertyTypes(properties, ['children'])).toEqual(['children']);
expect(getUnsupportedPropertyTypes(properties, ['children'])).toEqual([
'testProperty1',
'children',
]);
standeren marked this conversation as resolved.
Show resolved Hide resolved
});
it('Returns array of unsupported property keys when unsupported property keys are given', () => {
const properties = {
@@ -270,12 +285,25 @@ describe('Component utils', () => {
});

it('should return true if property ref is supported', () => {
expect(
isPropertyTypeSupported({
$ref: ExpressionSchemaBooleanDefinitionReference,
}),
).toBe(true);
['boolean', 'number', 'integer', 'string'].forEach((type) => {
expect(
isPropertyTypeSupported({
$ref: getExpressionSchemaDefinitionReference(type),
}),
).toBe(true);
});
});

it('should return false if property ref is supported', () => {
['object'].forEach((type) => {
expect(
isPropertyTypeSupported({
$ref: getExpressionSchemaDefinitionReference(type),
}),
).toBe(false);
});
});

it('should return true for property of array type with items that are type string', () => {
expect(
isPropertyTypeSupported({
@@ -286,10 +314,27 @@ describe('Component utils', () => {
}),
).toBe(true);
});
it('should return false for property type object', () => {
it('should return true for property type object with defined properties', () => {
expect(
isPropertyTypeSupported({
type: 'object',
properties: {
test: {
type: 'string',
},
},
}),
).toBe(true);
});

it('should return false for property type object with no defined properties, but additionalProperties defined', () => {
expect(
isPropertyTypeSupported({
type: 'object',
properties: {},
additionalProperties: {
type: 'string',
},
}),
).toBe(false);
});
22 changes: 18 additions & 4 deletions frontend/packages/ux-editor/src/utils/component.ts
Original file line number Diff line number Diff line change
@@ -116,8 +116,12 @@ 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;

export const getExpressionSchemaDefinitionReference = (type: string) => {
return `${EXPRESSION_SCHEMA_BASE_DEFINITION_REFERENCE}${type}`;
};

/**
* Gets an array of unsupported property keys
@@ -145,8 +149,10 @@ export const getUnsupportedPropertyTypes = (
return unsupportedPropertyKeys;
};

const supportedPropertyTypes = ['boolean', 'number', 'integer', 'string'];
const supportedPropertyRefs = [ExpressionSchemaBooleanDefinitionReference];
const supportedPropertyTypes = ['boolean', 'number', 'integer', 'string', 'object'];
const supportedPropertyRefs = supportedPropertyTypes
.filter((p) => p !== 'object')
.map((type) => getExpressionSchemaDefinitionReference(type));

/**
* Checks if a given property with optional property key is supported by component config view.
@@ -160,5 +166,13 @@ export const isPropertyTypeSupported = (property: KeyValuePairs) => {
if (property?.type === 'array' && property?.items?.type === 'string') {
return true;
}
if (
property?.type === 'object' &&
Object.keys(property?.properties).length === 0 &&
property?.additionalProperties
) {
return false;
}

return supportedPropertyTypes.includes(property?.type);
};

Unchanged files with check annotations Beta

initializeEditor();
initializeUnsavedChangesCount();
}, [bpmnXml, modelerRef, setBpmnDetails, setNumberOfUnsavedChanges]);

Check warning on line 59 in frontend/packages/process-editor/src/hooks/useBpmnEditor.ts

GitHub Actions / Typechecking and linting

React Hook useEffect has a missing dependency: 'getModeler'. Either include it or remove the dependency array
return { canvasRef, modelerRef };
};
state,
storeCreator,
});
const renderResult = render(renderComponent(component));

Check failure on line 86 in frontend/packages/ux-editor/src/testing/mocks.tsx

GitHub Actions / Testing

FormComponentConfig › should not render property if it is null

Expected test not to call console.error(). If the error is expected, test for it explicitly by mocking it out using jest.spyOn(console, 'error').mockImplementation() and test that the warning occurs. Error: Uncaught [TypeError: Cannot read properties of null (reading 'type')] at reportException (../node_modules/jsdom/lib/jsdom/living/helpers/runtime-script-errors.js:66:24) at innerInvokeEventListeners (../node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:353:9) at invokeEventListeners (../node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:286:3) at HTMLUnknownElementImpl._dispatch (../node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:233:9) at HTMLUnknownElementImpl.dispatchEvent (../node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:104:17) at HTMLUnknownElement.dispatchEvent (../node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:241:34) at Object.invokeGuardedCallbackDev (../node_modules/react-dom/cjs/react-dom.development.js:750:45) at invokeGuardedCallback (../node_modules/react-dom/cjs/react-dom.development.js:771:126) at beginWork$1 (../node_modules/react-dom/cjs/react-dom.development.js:4702:1) at performUnitOfWork (../node_modules/react-dom/cjs/react-dom.development.js:4518:270) at workLoopSync (../node_modules/react-dom/cjs/react-dom.development.js:4504:30) at renderRootSync (../node_modules/react-dom/cjs/react-dom.development.js:4500:159) at performConcurrentWorkOnRoot (../node_modules/react-dom/cjs/react-dom.development.js:4339:166) at flushActQueue (../node_modules/react/cjs/react.development.js:2337:28) at act (../node_modules/react/cjs/react.development.js:2261:15) at ../node_modules/@testing-library/react/dist/act-compat.js:69:25 at renderRoot (../node_modules/@testing-library/react/dist/pure.js:204:26) at render (../node_modules/@testing-library/react/dist/pure.js:291:10) at packages/ux-editor/src/testing/mocks.tsx:86:32 at queries (packages/ux-editor/src/components/config/FormComponentConfig.test.tsx:114:36) at Object.render (packages/ux-editor/src/components/config/FormComponentConfig.test.tsx:84:5) detail: TypeError: Cannot read properties of null (reading 'type') at type (packages/ux-editor/src/components/config/FormComponentConfig.tsx:64:25) at matcher (packages/ux-editor/src/components/config/FormComponentConfig.tsx:25:58) at Array.filter (<anonymous>) at filter (packages/ux-editor/src/components/config/FormComponentConfig.tsx:25:34) at getFilteredPropertyKeys (packages/ux-editor/src/components/config/FormComponentConfig.tsx:62:35) at renderWithHooks (../node_modules/react-dom/cjs/react-dom.development.js:2719:157) at mountIndeterminateComponent (../node_modules/react-dom/cjs/react-dom.development.js:3297:1445) at beginWork (../node_modules/react-dom/cjs/react-dom.development.js:3636:93) at HTMLUnknownElement.callCallback (../node_modules/react-dom/cjs/react-dom.development.js:730:119) at HTMLUnknownElement.callTheUserObjectsOperation (../node_modules/jsdom/lib/jsdom/living/generated/EventListener.js:26:30) at innerInvokeEventListeners (../node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:350:25) at invokeEventListeners (../node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:286:3) at HTMLUnknownElementImpl._dispatch (../node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:233:9) at HTMLUnknownElementImpl.dispatchEvent (../node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:104:17) at HTMLUnknownElement.dispatchEvent (../node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:241:34) at Object.invokeGuardedCallbackDev (../node_modules/react-dom/cjs/react-dom.development.js:750:45) at invokeGuardedCallback (../node_modules/react-dom/cjs/react-dom.development.js:771:126) at beginWork$1 (../node_modules/react-dom/cjs/react-dom.development.js:4702:1) at performUnitOfWork (../node_modules/react-do
const rerender = (rerenderedComponent) =>
renderResult.rerender(renderComponent(rerenderedComponent));
return { renderResult: { ...renderResult, rerender }, store };