Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/utils/src/schema/retrieveSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ export function resolveSchema<T = any, S extends StrictRJSFSchema = RJSFSchema,
expandAllBranches,
recurseList,
formData,
experimental_customMergeAllOf,
);
if (updatedSchemas.length > 1 || updatedSchemas[0] !== schema) {
// return the updatedSchemas array if it has either multiple schemas within it
Expand All @@ -255,6 +256,7 @@ export function resolveSchema<T = any, S extends StrictRJSFSchema = RJSFSchema,
expandAllBranches,
recurseList,
formData,
experimental_customMergeAllOf,
);
return resolvedSchemas.flatMap((s) => {
return retrieveSchemaInternal<T, S, F>(
Expand Down
344 changes: 344 additions & 0 deletions packages/utils/test/schema/retrieveSchemaTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1926,5 +1926,349 @@ export default function retrieveSchemaTest(testValidator: TestValidatorType) {
]);
});
});
describe('resolveReference() with experimental_customMergeAllOf', () => {
it('should pass experimental_customMergeAllOf parameter to retrieveSchemaInternal', () => {
const schema: RJSFSchema = {
$ref: '#/definitions/testRef',
allOf: [
{
type: 'object',
properties: {
string: { type: 'string' },
},
},
{
type: 'object',
properties: {
number: { type: 'number' },
},
},
],
};
const rootSchema: RJSFSchema = {
definitions: {
testRef: {
type: 'object',
properties: {
base: { type: 'string' },
},
},
},
};
const customMergeAllOf = jest.fn().mockReturnValue({
type: 'object',
properties: {
base: { type: 'string' },
string: { type: 'string' },
number: { type: 'number' },
},
});
const result = retrieveSchema(testValidator, schema, rootSchema, {}, customMergeAllOf);
expect(customMergeAllOf).toHaveBeenCalled();
expect(result).toEqual({
type: 'object',
properties: {
base: { type: 'string' },
string: { type: 'string' },
number: { type: 'number' },
},
});
});
});
describe('resolveDependencies() with experimental_customMergeAllOf', () => {
it('should pass experimental_customMergeAllOf parameter through dependency resolution', () => {
const schema: RJSFSchema = {
type: 'object',
properties: {
a: { type: 'string' },
},
dependencies: {
a: {
allOf: [
{
type: 'object',
properties: {
string: { type: 'string' },
},
},
{
type: 'object',
properties: {
number: { type: 'number' },
},
},
],
},
},
};
const rootSchema: RJSFSchema = { definitions: {} };
const formData = { a: 'test' };
const customMergeAllOf = jest.fn().mockReturnValue({
type: 'object',
properties: {
string: { type: 'string' },
number: { type: 'number' },
},
});
const result = retrieveSchema(testValidator, schema, rootSchema, formData, customMergeAllOf);
expect(customMergeAllOf).toHaveBeenCalled();
expect(result).toEqual({
type: 'object',
properties: {
a: { type: 'string' },
string: { type: 'string' },
number: { type: 'number' },
},
});
});
});
describe('resolveSchema() integration with experimental_customMergeAllOf', () => {
it('should properly pass experimental_customMergeAllOf through all resolution paths', () => {
const schema: RJSFSchema = {
$ref: '#/definitions/baseSchema',
dependencies: {
trigger: {
allOf: [
{
type: 'object',
properties: {
prop1: { type: 'string' },
},
},
{
type: 'object',
properties: {
prop2: { type: 'number' },
},
},
],
},
},
};
const rootSchema: RJSFSchema = {
definitions: {
baseSchema: {
type: 'object',
properties: {
base: { type: 'string' },
},
allOf: [
{
type: 'object',
properties: {
additional: { type: 'boolean' },
},
},
],
},
},
};
const formData = { trigger: 'value' };
const customMergeAllOf = jest.fn().mockImplementation((schema) => {
// Custom merge logic that combines all properties
const allProperties: any = {};
if (schema.properties) {
Object.assign(allProperties, schema.properties);
}
if (schema.allOf) {
schema.allOf.forEach((subSchema: any) => {
if (subSchema.properties) {
Object.assign(allProperties, subSchema.properties);
}
});
}
return {
...schema,
properties: allProperties,
allOf: undefined,
};
});
const result = retrieveSchema(testValidator, schema, rootSchema, formData, customMergeAllOf);
// Verify that customMergeAllOf was called multiple times (for different allOf blocks)
expect(customMergeAllOf).toHaveBeenCalledTimes(3);
expect(result).toEqual({
type: 'object',
properties: {
base: { type: 'string' },
additional: { type: 'boolean' },
},
allOf: undefined,
});
});
it('should handle experimental_customMergeAllOf with nested $ref resolution', () => {
const schema: RJSFSchema = {
$ref: '#/definitions/nestedRef',
};
const rootSchema: RJSFSchema = {
definitions: {
nestedRef: {
$ref: '#/definitions/finalSchema',
allOf: [
{
type: 'object',
properties: {
nested: { type: 'string' },
},
},
],
},
finalSchema: {
type: 'object',
properties: {
final: { type: 'number' },
},
},
},
};
const customMergeAllOf = jest.fn().mockReturnValue({
type: 'object',
properties: {
final: { type: 'number' },
nested: { type: 'string' },
},
});
const result = retrieveSchema(testValidator, schema, rootSchema, {}, customMergeAllOf);
expect(customMergeAllOf).toHaveBeenCalled();
expect(result).toEqual({
type: 'object',
properties: {
final: { type: 'number' },
nested: { type: 'string' },
},
});
});
});
describe('Edge cases for experimental_customMergeAllOf fix', () => {
it('should handle undefined experimental_customMergeAllOf parameter gracefully', () => {
const schema: RJSFSchema = {
$ref: '#/definitions/testRef',
dependencies: {
trigger: {
allOf: [
{
type: 'object',
properties: {
prop: { type: 'string' },
},
},
],
},
},
};
const rootSchema: RJSFSchema = {
definitions: {
testRef: {
type: 'object',
properties: {
base: { type: 'string' },
},
},
},
};
const formData = { trigger: 'value' };
// Test with undefined experimental_customMergeAllOf (should use default mergeAllOf)
const result = retrieveSchema(testValidator, schema, rootSchema, formData, undefined);
expect(result).toEqual({
type: 'object',
properties: {
base: { type: 'string' },
},
});
});
it('should handle experimental_customMergeAllOf that throws an error', () => {
const schema: RJSFSchema = {
allOf: [
{
type: 'object',
properties: {
string: { type: 'string' },
},
},
{
type: 'object',
properties: {
number: { type: 'number' },
},
},
],
};
const rootSchema: RJSFSchema = { definitions: {} };
const customMergeAllOf = jest.fn().mockImplementation(() => {
throw new Error('Custom merge failed');
});
const result = retrieveSchema(testValidator, schema, rootSchema, {}, customMergeAllOf);
// Should fall back to default behavior when custom merge fails
expect(result).toEqual({});
expect(consoleWarnSpy).toHaveBeenCalledWith('could not merge subschemas in allOf:\n', expect.any(Error));
});
it('should pass experimental_customMergeAllOf through complex nested resolution chains', () => {
const schema: RJSFSchema = {
$ref: '#/definitions/level1',
};
const rootSchema: RJSFSchema = {
definitions: {
level1: {
$ref: '#/definitions/level2',
dependencies: {
dep1: {
allOf: [
{
type: 'object',
properties: {
nested1: { type: 'string' },
},
},
],
},
},
},
level2: {
type: 'object',
properties: {
base: { type: 'string' },
},
allOf: [
{
type: 'object',
properties: {
additional: { type: 'number' },
},
},
],
},
},
};
const formData = { dep1: 'value' };
const customMergeAllOf = jest.fn().mockImplementation((schema) => {
const allProperties: any = {};
if (schema.properties) {
Object.assign(allProperties, schema.properties);
}
if (schema.allOf) {
schema.allOf.forEach((subSchema: any) => {
if (subSchema.properties) {
Object.assign(allProperties, subSchema.properties);
}
});
}
return {
...schema,
properties: allProperties,
allOf: undefined,
};
});
const result = retrieveSchema(testValidator, schema, rootSchema, formData, customMergeAllOf);
// Should be called for both allOf blocks (level2 and dependency)
expect(customMergeAllOf).toHaveBeenCalledTimes(3);
expect(result).toEqual({
type: 'object',
properties: {
base: { type: 'string' },
additional: { type: 'number' },
},
allOf: undefined,
});
});
});
});
}