Fix field extension validation not working when field is in dependencies in an array field

Signed-off-by: bnechyporenko <bnechyporenko@bol.com>
This commit is contained in:
bnechyporenko
2025-02-23 11:05:53 +01:00
parent b5a82087a7
commit 4d26652893
3 changed files with 140 additions and 2 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-react': patch
---
Fix field extension validation not working when field is in dependencies in an array field
@@ -17,6 +17,12 @@ import { JsonObject } from '@backstage/types';
import { CustomFieldValidator } from '../../../extensions';
import { createAsyncValidators } from './createAsyncValidators';
type UrlItem = {
icon?: string;
title?: string;
url: string;
};
describe('createAsyncValidators', () => {
it('should call the correct functions for validation', async () => {
const schema: JsonObject = {
@@ -402,6 +408,9 @@ describe('createAsyncValidators', () => {
});
it('should call validator for array object property from a custom field extension', async () => {
let validatorTriggered = false;
const validationErrors = [] as string[];
const schema: JsonObject = {
type: 'object',
properties: {
@@ -423,7 +432,23 @@ describe('createAsyncValidators', () => {
},
},
};
const validators = { CustomLinkField: jest.fn() };
const validators = {
CustomLinkField: (input: unknown) => {
const regex = /https?:\/\/(\S+)/;
const items = input instanceof Array ? input : [input];
items.forEach((item: UrlItem) => {
if (!regex.test(item.url)) {
validationErrors.push(
`Url ${item.url} is invalid. Urls must start with http:// or https://`,
);
}
});
validatorTriggered = true;
},
};
const validate = createAsyncValidators(schema, validators, {
apiHolder: { get: jest.fn() },
@@ -433,6 +458,84 @@ describe('createAsyncValidators', () => {
links: [{ url: 'http://my-url.spotify.com' }],
});
expect(validators.CustomLinkField).toHaveBeenCalled();
expect(validatorTriggered).toBe(true);
expect(validationErrors).toEqual([]);
});
it('should validate field in the dependencies in an array field', async () => {
const schema: JsonObject = {
title: 'Make a choice',
properties: {
myArray: {
type: 'array',
title: 'Array',
items: {
type: 'object',
required: ['selector'],
properties: {
selector: {
title: 'Selector',
type: 'string',
enum: ['Choice 1', 'Choice 2'],
},
},
dependencies: {
selector: {
oneOf: [
{ properties: { selector: { enum: ['Choice 1'] } } },
{
properties: {
selector: { enum: ['Choice 2'] },
customValidatedField: {
title: 'Custom validated field',
type: 'string',
'ui:field': 'ValidateKebabCase',
},
},
},
],
},
},
},
},
},
};
const validatorsForChoice1 = { ValidateKebabCase: jest.fn() };
const validateChoice1 = createAsyncValidators(
schema,
validatorsForChoice1,
{
apiHolder: { get: jest.fn() },
},
);
await validateChoice1({
myArray: [{ selector: 'Choice 1' }],
});
expect(validatorsForChoice1.ValidateKebabCase).not.toHaveBeenCalled();
const validatorsForChoice2 = { ValidateKebabCase: jest.fn() };
const validateChoice2 = createAsyncValidators(
schema,
validatorsForChoice2,
{
apiHolder: { get: jest.fn() },
},
);
await validateChoice2({
myArray: [
{
selector: 'Choice 2',
customValidatedField: 'apple',
},
],
});
expect(validatorsForChoice2.ValidateKebabCase).toHaveBeenCalled();
});
});
@@ -125,6 +125,30 @@ export const createAsyncValidators = (
}
};
const doValidateDependency = async (propValue: JsonObject) => {
const { schema: itemsSchema, uiSchema: itemsUiSchema } =
extractSchemaFromStep(propValue);
await doValidateItem(propValue, itemsSchema, itemsUiSchema);
const iterable = Array.isArray(value) ? value : [value];
for (const item of iterable) {
if (item && isObject(item)) {
const keys = Object.keys(item);
for (const k of keys) {
if (itemsUiSchema[k] && 'ui:field' in itemsUiSchema[k]) {
await validateForm(
itemsUiSchema[k]['ui:field'],
k,
item[k],
itemsSchema,
itemsUiSchema,
);
}
}
}
}
};
if ('ui:field' in definitionInSchema) {
await doValidateItem(definitionInSchema, schema, uiSchema);
} else if (hasItems && 'ui:field' in definitionInSchema.items) {
@@ -135,6 +159,12 @@ export const createAsyncValidators = (
for (const [, propValue] of Object.entries(properties)) {
await doValidate(propValue);
}
const dependencies = (definitionInSchema.items?.dependencies ??
[]) as JsonObject[];
for (const [, propValue] of Object.entries(dependencies)) {
await doValidateDependency(propValue);
}
} else if (isObject(value)) {
formValidation[key] = await validate(formData, pointer, value);
}