Skip to content

Commit 1faa4c1

Browse files
authored
fix(dashboard): Treat readonly custom fields as optional in Zod schema (#5057)
Readonly custom fields with nullable: false produced an unsatisfiable Zod schema, permanently disabling the Create/Update button. Fixes #5045
1 parent c6076eb commit 1faa4c1

2 files changed

Lines changed: 49 additions & 1 deletion

File tree

packages/dashboard/src/lib/framework/form-engine/form-schema-tools.spec.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ const createMockCustomField = (
4141
datetimeMax?: string;
4242
list?: boolean;
4343
nullable?: boolean;
44+
readonly?: boolean;
4445
} = {},
4546
) => ({
4647
name,
@@ -305,6 +306,53 @@ describe('form-schema-tools', () => {
305306
expect(() => schema.parse(undefinedData)).not.toThrow();
306307
});
307308

309+
// Readonly custom fields with nullable: false should not block form submission.
310+
// The server excludes readonly fields from Create/Update GraphQL input types,
311+
// and the dashboard strips them from the mutation payload before submitting.
312+
// So the Zod schema must treat them as optional.
313+
it('should treat readonly custom fields as optional even when nullable is false', () => {
314+
const fields = [createMockField('customFields', 'Object', false, false, [])];
315+
const customFields = [
316+
createMockCustomField('type', 'string', { nullable: false, readonly: true }),
317+
];
318+
319+
const schema = createFormSchemaFromFields(fields, customFields, false);
320+
321+
// Should accept null/undefined since readonly fields cannot be submitted
322+
const nullData = { customFields: { type: null } };
323+
expect(() => schema.parse(nullData)).not.toThrow();
324+
325+
const undefinedData = { customFields: { type: undefined } };
326+
expect(() => schema.parse(undefinedData)).not.toThrow();
327+
328+
const emptyData = { customFields: {} };
329+
expect(() => schema.parse(emptyData)).not.toThrow();
330+
331+
// Should still accept a value if one happens to be present
332+
const withValue = { customFields: { type: 'manually-created' } };
333+
expect(() => schema.parse(withValue)).not.toThrow();
334+
});
335+
336+
// Guards against the readonly fix accidentally making every field optional.
337+
// A plain nullable: false field (no readonly) must still reject null/undefined.
338+
it('should reject null/undefined for non-readonly custom fields with nullable false', () => {
339+
const fields = [createMockField('customFields', 'Object', false, false, [])];
340+
const customFields = [
341+
createMockCustomField('sku', 'string', { nullable: false }),
342+
];
343+
344+
const schema = createFormSchemaFromFields(fields, customFields, false);
345+
346+
const validData = { customFields: { sku: 'AB-123' } };
347+
expect(() => schema.parse(validData)).not.toThrow();
348+
349+
const nullData = { customFields: { sku: null } };
350+
expect(() => schema.parse(nullData)).toThrow();
351+
352+
const undefinedData = { customFields: { sku: undefined } };
353+
expect(() => schema.parse(undefinedData)).toThrow();
354+
});
355+
308356
it('should only include non-translatable fields in root context', () => {
309357
const fields = [createMockField('customFields', 'Object', false, false, [])];
310358
const customFields = [

packages/dashboard/src/lib/framework/form-engine/form-schema-tools.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ function applyCustomFieldModifiers(zodType: ZodType, customField: CustomFieldCon
257257
if (customField.list) {
258258
modifiedType = z.array(modifiedType);
259259
}
260-
if (customField.nullable !== false) {
260+
if (customField.nullable !== false || customField.readonly) {
261261
modifiedType = modifiedType.optional().nullable();
262262
}
263263
if (customField.readonly) {

0 commit comments

Comments
 (0)