import { describe, expect, test } from 'vitest'; import { FileField, RelationField, SelectField } from '../types.js'; import { getFieldType } from './field-helpers.js'; // Mock the getZodFieldType function since it's not exported // We'll test it by creating a simple wrapper function testZodFieldType(field: any, schemaType: 'response' | 'create' | 'update'): string | null { // This is a simplified version of the logic for testing const isOptional = schemaType === 'update' ? field.name !== 'id' : !field.required || field.name === 'id'; const optionalSuffix = isOptional ? '.optional()' : ''; if (field.type === 'relation') { const single = field.maxSelect === 1; const stringSchema = 'z.string()'; if (schemaType === 'response') { const schema = single ? stringSchema : `z.array(${stringSchema})`; return `${field.name}: ${schema}${optionalSuffix}`; } else { const schema = single ? stringSchema : `z.union([${stringSchema}, z.array(${stringSchema})])`; return `${field.name}: ${schema}${optionalSuffix}`; } } return null; } describe('Relation Field Logic Fix', () => { test('maxSelect=1 should be treated as single relation', () => { const columns = { create: [] as string[], update: [] as string[], response: [] as string[] }; const relationField: RelationField = { id: 'property', name: 'property', type: 'relation', system: false, required: false, presentable: false, unique: false, options: {}, hidden: false, collectionId: 'properties', cascadeDelete: false, minSelect: null, maxSelect: 1, // Single relation displayFields: null }; getFieldType(relationField, columns); // Should generate single relation types (string, not Array) expect(columns.response).toContain('property: string;'); expect(columns.create).toContain('property?: string;'); expect(columns.update).toContain('property?: string;'); // Should NOT have array modifiers for single relations expect(columns.update.some(field => field.includes("'property+'"))).toBe(false); expect(columns.update.some(field => field.includes("'property-'"))).toBe(false); }); test('maxSelect=0 should be treated as multi relation', () => { const columns = { create: [] as string[], update: [] as string[], response: [] as string[] }; const relationField: RelationField = { id: 'properties', name: 'properties', type: 'relation', system: false, required: false, presentable: false, unique: false, options: {}, hidden: false, collectionId: 'properties', cascadeDelete: false, minSelect: null, maxSelect: 0, // Unlimited = multi relation displayFields: null }; getFieldType(relationField, columns); // Should generate multi relation types (Array) expect(columns.response).toContain('properties: Array;'); expect(columns.create).toContain('properties?: MaybeArray;'); expect(columns.update).toContain('properties?: MaybeArray;'); // Should have array modifiers for multi relations expect(columns.update.some(field => field.includes("'properties+'"))).toBe(true); expect(columns.update.some(field => field.includes("'properties-'"))).toBe(true); }); test('maxSelect=2 should be treated as multi relation', () => { const columns = { create: [] as string[], update: [] as string[], response: [] as string[] }; const relationField: RelationField = { id: 'tags', name: 'tags', type: 'relation', system: false, required: false, presentable: false, unique: false, options: {}, hidden: false, collectionId: 'tags', cascadeDelete: false, minSelect: null, maxSelect: 2, // Multiple relations displayFields: null }; getFieldType(relationField, columns); // Should generate multi relation types (Array) expect(columns.response).toContain('tags: Array;'); expect(columns.create).toContain('tags?: MaybeArray;'); expect(columns.update).toContain('tags?: MaybeArray;'); // Should have array modifiers for multi relations expect(columns.update.some(field => field.includes("'tags+'"))).toBe(true); expect(columns.update.some(field => field.includes("'tags-'"))).toBe(true); }); test('Zod: maxSelect=1 should generate single relation schema', () => { const relationField = { name: 'property', type: 'relation', required: false, maxSelect: 1 }; const responseSchema = testZodFieldType(relationField, 'response'); const createSchema = testZodFieldType(relationField, 'create'); expect(responseSchema).toBe('property: z.string().optional()'); expect(createSchema).toBe('property: z.string().optional()'); }); test('Zod: maxSelect=0 should generate multi relation schema', () => { const relationField = { name: 'properties', type: 'relation', required: false, maxSelect: 0 }; const responseSchema = testZodFieldType(relationField, 'response'); const createSchema = testZodFieldType(relationField, 'create'); expect(responseSchema).toBe('properties: z.array(z.string()).optional()'); expect(createSchema).toBe('properties: z.union([z.string(), z.array(z.string())]).optional()'); }); }); describe('Select Field Logic Fix', () => { test('maxSelect=1 should be treated as single select', () => { const columns = { create: [] as string[], update: [] as string[], response: [] as string[] }; const selectField: SelectField = { id: 'status', name: 'status', type: 'select', system: false, required: false, presentable: false, unique: false, options: {}, hidden: false, maxSelect: 1, values: ['draft', 'published', 'archived'] }; getFieldType(selectField, columns); // Should generate single select types expect(columns.response.some(field => field.includes("status: '' | 'draft' | 'published' | 'archived'") )).toBe(true); }); test('maxSelect=0 should be treated as multi select', () => { const columns = { create: [] as string[], update: [] as string[], response: [] as string[] }; const selectField: SelectField = { id: 'tags', name: 'tags', type: 'select', system: false, required: false, presentable: false, unique: false, options: {}, hidden: false, maxSelect: 0, // Unlimited = multi select values: ['tag1', 'tag2', 'tag3'] }; getFieldType(selectField, columns); // Should generate multi select types (Array) expect(columns.response.some(field => field.includes("Array<'tag1' | 'tag2' | 'tag3'>") )).toBe(true); }); }); describe('File Field Logic Fix', () => { test('maxSelect=1 should be treated as single file', () => { const columns = { create: [] as string[], update: [] as string[], response: [] as string[] }; const fileField: FileField = { id: 'avatar', name: 'avatar', type: 'file', system: false, required: false, presentable: false, unique: false, options: {}, hidden: false, maxSelect: 1, maxSize: 1000000, mimeTypes: ['image/*'], thumbs: null, protected: false }; getFieldType(fileField, columns); // Should generate single file types expect(columns.response).toContain('avatar: string;'); expect(columns.create).toContain('avatar?: File | null;'); expect(columns.update).toContain('avatar?: File | null;'); }); test('maxSelect=0 should be treated as multi file', () => { const columns = { create: [] as string[], update: [] as string[], response: [] as string[] }; const fileField: FileField = { id: 'attachments', name: 'attachments', type: 'file', system: false, required: false, presentable: false, unique: false, options: {}, hidden: false, maxSelect: 0, // Unlimited = multi file maxSize: 1000000, mimeTypes: ['*/*'], thumbs: null, protected: false }; getFieldType(fileField, columns); // Should generate multi file types expect(columns.response).toContain('attachments: MaybeArray;'); expect(columns.create).toContain('attachments?: MaybeArray;'); expect(columns.update).toContain('attachments?: MaybeArray;'); }); });