Skip to content

Commit 62b23cb

Browse files
committed
test: add unit tests for real Zod v4 $ref + sibling patterns
Add 3 tests covering real-world Zod v4 output patterns: - $ref with multiple metadata siblings (.meta() pattern) - $ref with default value sibling (.default() pattern) - $def referencing another $def (nested registered types) These verify the sibling merge path handles all real schema generator output correctly. Exhaustive cross-library testing (Zod v4, ArkType, Valibot) confirmed no generator produces $ref with siblings containing nested $ref.
1 parent 9b42858 commit 62b23cb

1 file changed

Lines changed: 68 additions & 0 deletions

File tree

packages/core/test/schema.test.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,4 +128,72 @@ describe('dereferenceLocalRefs', () => {
128128
};
129129
expect(() => dereferenceLocalRefs(schema)).toThrow(/Recursive schema detected/);
130130
});
131+
132+
// The following tests cover real Zod v4 output patterns where $ref appears
133+
// with sibling keywords. Zod only produces metadata siblings (description,
134+
// title, default, etc.) — never schema nodes containing nested $ref.
135+
// These prove the sibling merge path handles all real-world scenarios.
136+
137+
test('$ref with multiple metadata siblings (Zod .meta() on registered type)', () => {
138+
const schema = {
139+
type: 'object',
140+
properties: {
141+
home: { $ref: '#/$defs/Address', title: 'Home', deprecated: true }
142+
},
143+
$defs: {
144+
Address: { type: 'object', properties: { street: { type: 'string' } } }
145+
}
146+
};
147+
const result = dereferenceLocalRefs(schema);
148+
const home = (result['properties'] as Record<string, Record<string, unknown>>)['home']!;
149+
expect(home['type']).toBe('object');
150+
expect(home['title']).toBe('Home');
151+
expect(home['deprecated']).toBe(true);
152+
expect(JSON.stringify(result)).not.toContain('$ref');
153+
});
154+
155+
test('$ref with default value sibling (Zod .default() on registered type)', () => {
156+
const schema = {
157+
type: 'object',
158+
properties: {
159+
home: { $ref: '#/$defs/Address', default: { street: '123 Main' } }
160+
},
161+
$defs: {
162+
Address: { type: 'object', properties: { street: { type: 'string' } } }
163+
}
164+
};
165+
const result = dereferenceLocalRefs(schema);
166+
const home = (result['properties'] as Record<string, Record<string, unknown>>)['home']!;
167+
expect(home['type']).toBe('object');
168+
expect(home['default']).toEqual({ street: '123 Main' });
169+
expect(JSON.stringify(result)).not.toContain('$ref');
170+
});
171+
172+
test('$def referencing another $def (nested registered types)', () => {
173+
const schema = {
174+
type: 'object',
175+
properties: {
176+
employer: { $ref: '#/$defs/Company', description: 'The company' }
177+
},
178+
$defs: {
179+
Address: { type: 'object', properties: { street: { type: 'string' } } },
180+
Company: {
181+
type: 'object',
182+
properties: {
183+
name: { type: 'string' },
184+
hq: { $ref: '#/$defs/Address' }
185+
}
186+
}
187+
}
188+
};
189+
const result = dereferenceLocalRefs(schema);
190+
expect(JSON.stringify(result)).not.toContain('$ref');
191+
expect(JSON.stringify(result)).not.toContain('$defs');
192+
const employer = (result['properties'] as Record<string, Record<string, unknown>>)['employer']!;
193+
expect(employer['description']).toBe('The company');
194+
expect(employer['type']).toBe('object');
195+
const hq = (employer['properties'] as Record<string, Record<string, unknown>>)['hq']!;
196+
expect(hq['type']).toBe('object');
197+
expect(hq['properties']).toEqual({ street: { type: 'string' } });
198+
});
131199
});

0 commit comments

Comments
 (0)