import { JSONSchemaService } from './JsonSchemaService'; import { ComponentInputMetaSchema } from './JsonSchemaService'; import { TypeResolverBuilder } from './jsonTypeResolution/TypeResolverBuilder'; describe('JSONSchemaService - hasAllOfCombinator', () => { let jsonSchemaService: JSONSchemaService; beforeAll(() => { const typeResolverBuilder = new TypeResolverBuilder(); const typeResolver = typeResolverBuilder.build(); jsonSchemaService = new JSONSchemaService(typeResolver, ComponentInputMetaSchema); }); // Helper to access private method const hasAllOfCombinator = (schema: any) => (jsonSchemaService as any).hasAllOfCombinator(schema); describe('Direct allOf detection', () => { it('should detect direct allOf usage', () => { const schema = { type: 'object', allOf: [{ properties: { name: { type: 'string' } } }, { properties: { age: { type: 'number' } } }], }; expect(hasAllOfCombinator(schema)).toBe(true); }); it('should detect empty allOf array', () => { const schema = { type: 'object', allOf: [], }; expect(hasAllOfCombinator(schema)).toBe(true); }); }); describe('Nested allOf detection', () => { it('should detect allOf in properties', () => { const schema = { type: 'object', properties: { user: { allOf: [{ properties: { name: { type: 'string' } } }, { properties: { email: { type: 'string' } } }], }, }, }; expect(hasAllOfCombinator(schema)).toBe(true); }); it('should detect allOf in deeply nested properties', () => { const schema = { type: 'object', properties: { componentContent: { type: 'object', properties: { heroSection: { allOf: [ { properties: { title: { type: 'string' } } }, { properties: { subtitle: { type: 'string' } } }, ], }, }, }, }, }; expect(hasAllOfCombinator(schema)).toBe(true); }); it('should detect allOf in array items', () => { const schema = { type: 'object', properties: { items: { type: 'array', items: { allOf: [{ properties: { id: { type: 'number' } } }, { properties: { name: { type: 'string' } } }], }, }, }, }; expect(hasAllOfCombinator(schema)).toBe(true); }); }); describe('Combinator traversal', () => { it('should detect allOf within oneOf', () => { const schema = { type: 'object', oneOf: [ { allOf: [{ properties: { type: { const: 'A' } } }, { properties: { valueA: { type: 'string' } } }], }, { properties: { type: { const: 'B' } } }, ], }; expect(hasAllOfCombinator(schema)).toBe(true); }); it('should detect allOf within anyOf', () => { const schema = { type: 'object', anyOf: [ { properties: { simple: { type: 'string' } } }, { allOf: [{ properties: { complex: { type: 'object' } } }, { properties: { nested: { type: 'array' } } }], }, ], }; expect(hasAllOfCombinator(schema)).toBe(true); }); it('should detect allOf within not schema', () => { const schema = { type: 'object', not: { allOf: [{ properties: { forbidden: { type: 'string' } } }, { properties: { invalid: { type: 'number' } } }], }, }; expect(hasAllOfCombinator(schema)).toBe(true); }); }); describe('Conditional schema detection', () => { it('should detect allOf in if condition', () => { const schema = { type: 'object', if: { allOf: [{ properties: { type: { const: 'special' } } }, { properties: { mode: { const: 'advanced' } } }], }, then: { properties: { specialValue: { type: 'string' } } }, }; expect(hasAllOfCombinator(schema)).toBe(true); }); it('should detect allOf in then branch', () => { const schema = { type: 'object', if: { properties: { type: { const: 'special' } } }, then: { allOf: [{ properties: { requiredProp: { type: 'string' } } }, { required: ['requiredProp'] }], }, }; expect(hasAllOfCombinator(schema)).toBe(true); }); it('should detect allOf in else branch', () => { const schema = { type: 'object', if: { properties: { type: { const: 'simple' } } }, then: { properties: { simpleValue: { type: 'string' } } }, else: { allOf: [ { properties: { complexValue: { type: 'object' } } }, { properties: { metadata: { type: 'object' } } }, ], }, }; expect(hasAllOfCombinator(schema)).toBe(true); }); }); describe('Complex nested scenarios', () => { it('should detect allOf in Hero Banner-like schema', () => { const heroBannerSchema = { type: 'object', properties: { componentContent: { allOf: [ { if: { properties: { heroType: { const: 'Supporting content' } } }, then: { type: 'object', properties: { heroType: { type: 'string' }, heading: { type: 'string' }, content: { type: 'string' }, links: { type: 'array', items: { type: 'object', properties: { text: { type: 'string' }, url: { type: 'string' }, target: { type: 'string' }, }, }, }, }, }, }, { if: { properties: { heroType: { const: 'Supporting image' } } }, then: { properties: { image: { type: 'object' }, }, }, }, ], }, }, }; expect(hasAllOfCombinator(heroBannerSchema)).toBe(true); }); it('should detect allOf in very deeply nested structure', () => { const deepSchema = { type: 'object', properties: { level1: { properties: { level2: { oneOf: [ { properties: { level3: { anyOf: [ { allOf: [{ properties: { deep: { type: 'string' } } }], }, ], }, }, }, ], }, }, }, }, }; expect(hasAllOfCombinator(deepSchema)).toBe(true); }); }); describe('Negative cases - should return false', () => { it('should return false for simple object schema', () => { const schema = { type: 'object', properties: { name: { type: 'string' }, age: { type: 'number' }, }, required: ['name'], }; expect(hasAllOfCombinator(schema)).toBe(false); }); it('should return false for schema with only oneOf', () => { const schema = { type: 'object', oneOf: [{ properties: { typeA: { type: 'string' } } }, { properties: { typeB: { type: 'number' } } }], }; expect(hasAllOfCombinator(schema)).toBe(false); }); it('should return false for schema with only anyOf', () => { const schema = { type: 'object', anyOf: [{ properties: { option1: { type: 'string' } } }, { properties: { option2: { type: 'boolean' } } }], }; expect(hasAllOfCombinator(schema)).toBe(false); }); it('should return false for array schema without allOf', () => { const schema = { type: 'array', items: { type: 'object', properties: { id: { type: 'number' }, name: { type: 'string' }, }, }, }; expect(hasAllOfCombinator(schema)).toBe(false); }); it('should return false for conditional schema without allOf', () => { const schema = { type: 'object', if: { properties: { type: { const: 'special' } } }, then: { properties: { specialValue: { type: 'string' } } }, else: { properties: { normalValue: { type: 'string' } } }, }; expect(hasAllOfCombinator(schema)).toBe(false); }); }); describe('Edge cases', () => { it('should return false for null', () => { expect(hasAllOfCombinator(null)).toBe(false); }); it('should return false for undefined', () => { expect(hasAllOfCombinator(undefined)).toBe(false); }); it('should return false for string', () => { expect(hasAllOfCombinator('not an object')).toBe(false); }); it('should return false for number', () => { expect(hasAllOfCombinator(42)).toBe(false); }); it('should return false for boolean', () => { expect(hasAllOfCombinator(true)).toBe(false); }); it('should return false for array', () => { expect(hasAllOfCombinator(['not', 'a', 'schema'])).toBe(false); }); it('should return false for empty object', () => { expect(hasAllOfCombinator({})).toBe(false); }); it('should handle circular references gracefully', () => { const schema: any = { type: 'object', properties: { self: null, }, }; schema.properties.self = schema; // Create circular reference // Should not crash and should return false (no allOf) expect(hasAllOfCombinator(schema)).toBe(false); }); it('should handle circular references with allOf', () => { const schema: any = { type: 'object', allOf: [{ properties: { name: { type: 'string' } } }], properties: { self: null, }, }; schema.properties.self = schema; // Create circular reference // Should detect allOf despite circular reference expect(hasAllOfCombinator(schema)).toBe(true); }); it('should handle deeply circular references', () => { const schemaA: any = { type: 'object', properties: { b: null, }, }; const schemaB: any = { type: 'object', properties: { a: schemaA, c: null, }, }; const schemaC: any = { type: 'object', properties: { b: schemaB, }, }; // Create circular chain: A -> B -> C -> B schemaA.properties.b = schemaB; schemaB.properties.c = schemaC; // Should not crash and should return false (no allOf in any of them) expect(hasAllOfCombinator(schemaA)).toBe(false); expect(hasAllOfCombinator(schemaB)).toBe(false); expect(hasAllOfCombinator(schemaC)).toBe(false); }); it('should handle malformed properties object', () => { const schema = { type: 'object', properties: null, // Malformed properties }; expect(hasAllOfCombinator(schema)).toBe(false); }); }); describe('Performance characteristics', () => { it('should quickly process large schema without allOf', () => { const largeSchema = { type: 'object', properties: {}, }; // Generate 100 properties for (let i = 0; i < 100; i++) { (largeSchema.properties as any)[`prop${i}`] = { type: 'object', properties: { [`nested${i}`]: { type: 'string' }, [`array${i}`]: { type: 'array', items: { type: 'number' }, }, }, }; } const startTime = performance.now(); const result = hasAllOfCombinator(largeSchema); const endTime = performance.now(); expect(result).toBe(false); expect(endTime - startTime).toBeLessThan(10); // Should be very fast (< 10ms) }); it('should quickly detect allOf in large schema', () => { const largeSchemaWithAllOf = { type: 'object', allOf: [{ properties: { detected: { type: 'string' } } }], properties: {}, }; // Generate 100 properties for (let i = 0; i < 100; i++) { (largeSchemaWithAllOf.properties as any)[`prop${i}`] = { type: 'string', }; } const startTime = performance.now(); const result = hasAllOfCombinator(largeSchemaWithAllOf); const endTime = performance.now(); expect(result).toBe(true); expect(endTime - startTime).toBeLessThan(5); // Should be very fast due to early detection }); }); });