import { describe, it, expect } from 'vitest' import { toFieldString, fieldKey, resolveFieldName, resolveFieldType, resolveRequiredFlag, resolveOptions, resolveDefaultValue, formatDefaultValue, normalizeCustomField, normalizeCustomFieldInputs, extractStoredCustomFieldValue, buildCustomFieldInputs, requiredCustomFieldsFilled, shouldPersistField, formatValueForPersistence, buildCustomFieldMetadata, type CustomFieldInput, } from '~/shared/utils/customFieldFormUtils' // --------------------------------------------------------------------------- // toFieldString // --------------------------------------------------------------------------- describe('toFieldString', () => { it('returns empty string for null', () => { expect(toFieldString(null)).toBe('') }) it('returns empty string for undefined', () => { expect(toFieldString(undefined)).toBe('') }) it('returns string as-is', () => { expect(toFieldString('hello')).toBe('hello') }) it('converts number to string', () => { expect(toFieldString(42)).toBe('42') }) it('converts boolean to string', () => { expect(toFieldString(true)).toBe('true') expect(toFieldString(false)).toBe('false') }) it('returns empty string for objects', () => { expect(toFieldString({ foo: 'bar' })).toBe('') }) }) // --------------------------------------------------------------------------- // fieldKey // --------------------------------------------------------------------------- describe('fieldKey', () => { it('prefers customFieldValueId', () => { const field = { customFieldValueId: 'cfv-1', id: 'id-1', name: 'field' } as CustomFieldInput expect(fieldKey(field, 0)).toBe('cfv-1') }) it('falls back to id', () => { const field = { customFieldValueId: null, id: 'id-1', name: 'field' } as CustomFieldInput expect(fieldKey(field, 0)).toBe('id-1') }) it('falls back to name-index', () => { const field = { customFieldValueId: null, id: null, name: 'weight' } as CustomFieldInput expect(fieldKey(field, 3)).toBe('weight-3') }) }) // --------------------------------------------------------------------------- // resolveFieldName // --------------------------------------------------------------------------- describe('resolveFieldName', () => { it('resolves from name property', () => { expect(resolveFieldName({ name: 'Poids' })).toBe('Poids') }) it('falls back to key', () => { expect(resolveFieldName({ key: 'poids' })).toBe('poids') }) it('falls back to label', () => { expect(resolveFieldName({ label: 'Poids' })).toBe('Poids') }) it('returns empty string for empty object', () => { expect(resolveFieldName({})).toBe('') }) it('returns empty string for null', () => { expect(resolveFieldName(null)).toBe('') }) it('trims whitespace', () => { expect(resolveFieldName({ name: ' Poids ' })).toBe('Poids') }) }) // --------------------------------------------------------------------------- // resolveFieldType // --------------------------------------------------------------------------- describe('resolveFieldType', () => { it('resolves valid type', () => { expect(resolveFieldType({ type: 'number' })).toBe('number') }) it('resolves case-insensitive', () => { expect(resolveFieldType({ type: 'SELECT' })).toBe('select') }) it('falls back to text for unknown type', () => { expect(resolveFieldType({ type: 'blob' })).toBe('text') }) it('resolves nested value.type', () => { expect(resolveFieldType({ value: { type: 'date' } })).toBe('date') }) it('returns text for missing type', () => { expect(resolveFieldType({})).toBe('text') }) it('handles all allowed types', () => { for (const type of ['text', 'number', 'select', 'boolean', 'date']) { expect(resolveFieldType({ type })).toBe(type) } }) }) // --------------------------------------------------------------------------- // resolveRequiredFlag // --------------------------------------------------------------------------- describe('resolveRequiredFlag', () => { it('resolves boolean true', () => { expect(resolveRequiredFlag({ required: true })).toBe(true) }) it('resolves boolean false', () => { expect(resolveRequiredFlag({ required: false })).toBe(false) }) it('resolves nested value.required', () => { expect(resolveRequiredFlag({ value: { required: true } })).toBe(true) }) it('resolves string "true"', () => { expect(resolveRequiredFlag({ value: { required: 'true' } })).toBe(true) }) it('resolves string "1"', () => { expect(resolveRequiredFlag({ value: { required: '1' } })).toBe(true) }) it('defaults to false', () => { expect(resolveRequiredFlag({})).toBe(false) }) }) // --------------------------------------------------------------------------- // resolveOptions // --------------------------------------------------------------------------- describe('resolveOptions', () => { it('resolves array of strings', () => { expect(resolveOptions({ options: ['A', 'B'] })).toEqual(['A', 'B']) }) it('resolves array of objects with value key', () => { expect(resolveOptions({ options: [{ value: 'A' }, { value: 'B' }] })).toEqual(['A', 'B']) }) it('resolves array of objects with label key', () => { expect(resolveOptions({ options: [{ label: 'Foo' }] })).toEqual(['Foo']) }) it('falls back to value.options', () => { expect(resolveOptions({ value: { options: ['X'] } })).toEqual(['X']) }) it('falls back to value.choices', () => { expect(resolveOptions({ value: { choices: ['Y'] } })).toEqual(['Y']) }) it('returns empty array for no options', () => { expect(resolveOptions({})).toEqual([]) }) it('filters out empty strings', () => { expect(resolveOptions({ options: ['A', '', 'B'] })).toEqual(['A', 'B']) }) it('filters out null values', () => { expect(resolveOptions({ options: [null, 'A'] })).toEqual(['A']) }) }) // --------------------------------------------------------------------------- // resolveDefaultValue // --------------------------------------------------------------------------- describe('resolveDefaultValue', () => { it('returns null for null input', () => { expect(resolveDefaultValue(null)).toBeNull() }) it('resolves defaultValue', () => { expect(resolveDefaultValue({ defaultValue: 'hello' })).toBe('hello') }) it('resolves value (non-object)', () => { expect(resolveDefaultValue({ value: 42 })).toBe(42) }) it('resolves nested value.defaultValue', () => { expect(resolveDefaultValue({ value: { defaultValue: 'nested' } })).toBe('nested') }) it('returns null when nothing found', () => { expect(resolveDefaultValue({})).toBeNull() }) }) // --------------------------------------------------------------------------- // formatDefaultValue // --------------------------------------------------------------------------- describe('formatDefaultValue', () => { it('returns empty string for null', () => { expect(formatDefaultValue('text', null)).toBe('') }) it('converts number to string', () => { expect(formatDefaultValue('number', 42)).toBe('42') }) it('handles boolean type with true', () => { expect(formatDefaultValue('boolean', 'true')).toBe('true') expect(formatDefaultValue('boolean', true)).toBe('true') expect(formatDefaultValue('boolean', '1')).toBe('true') }) it('handles boolean type with false', () => { expect(formatDefaultValue('boolean', 'false')).toBe('false') expect(formatDefaultValue('boolean', false)).toBe('false') expect(formatDefaultValue('boolean', '0')).toBe('false') }) it('unwraps nested defaultValue object', () => { expect(formatDefaultValue('text', { defaultValue: 'inner' })).toBe('inner') }) }) // --------------------------------------------------------------------------- // normalizeCustomField // --------------------------------------------------------------------------- describe('normalizeCustomField', () => { it('normalizes a complete field', () => { const result = normalizeCustomField({ id: 'cf-1', name: 'Weight', type: 'number', required: true, options: [], orderIndex: 2, }) expect(result).toEqual({ id: 'cf-1', name: 'Weight', type: 'number', required: true, options: [], value: '', customFieldId: 'cf-1', customFieldValueId: null, orderIndex: 2, }) }) it('returns null for null input', () => { expect(normalizeCustomField(null)).toBeNull() }) it('returns null for field without name', () => { expect(normalizeCustomField({ type: 'text' })).toBeNull() }) it('uses fallback index', () => { const result = normalizeCustomField({ name: 'Test' }, 5) expect(result?.orderIndex).toBe(5) }) it('defaults type to text', () => { const result = normalizeCustomField({ name: 'Field' }) expect(result?.type).toBe('text') }) }) // --------------------------------------------------------------------------- // normalizeCustomFieldInputs // --------------------------------------------------------------------------- describe('normalizeCustomFieldInputs', () => { it('returns empty array for null structure', () => { expect(normalizeCustomFieldInputs(null)).toEqual([]) }) it('returns empty array for structure without customFields', () => { expect(normalizeCustomFieldInputs({})).toEqual([]) }) it('normalizes and sorts fields by orderIndex', () => { const result = normalizeCustomFieldInputs({ customFields: [ { name: 'B', orderIndex: 2 }, { name: 'A', orderIndex: 1 }, ], }) expect(result).toHaveLength(2) expect(result[0].name).toBe('A') expect(result[1].name).toBe('B') }) it('filters out invalid fields', () => { const result = normalizeCustomFieldInputs({ customFields: [{ name: 'Valid' }, null, { type: 'text' }], }) expect(result).toHaveLength(1) expect(result[0].name).toBe('Valid') }) }) // --------------------------------------------------------------------------- // extractStoredCustomFieldValue // --------------------------------------------------------------------------- describe('extractStoredCustomFieldValue', () => { it('returns string directly', () => { expect(extractStoredCustomFieldValue('hello')).toBe('hello') }) it('returns number directly', () => { expect(extractStoredCustomFieldValue(42)).toBe(42) }) it('returns empty string for null', () => { expect(extractStoredCustomFieldValue(null)).toBe('') }) it('extracts .value from object', () => { expect(extractStoredCustomFieldValue({ value: 'test' })).toBe('test') }) it('extracts nested .value.value', () => { expect(extractStoredCustomFieldValue({ value: { value: 'deep' } })).toBe('deep') }) it('extracts customFieldValue.value', () => { expect(extractStoredCustomFieldValue({ customFieldValue: { value: 'cfv' } })).toBe('cfv') }) }) // --------------------------------------------------------------------------- // buildCustomFieldInputs // --------------------------------------------------------------------------- describe('buildCustomFieldInputs', () => { const definitions = { customFields: [ { name: 'Weight', type: 'number', required: true, id: 'cf-1', orderIndex: 0 }, { name: 'Color', type: 'select', options: ['Red', 'Blue'], id: 'cf-2', orderIndex: 1 }, ], } it('builds inputs from definitions without values', () => { const result = buildCustomFieldInputs(definitions, null) expect(result).toHaveLength(2) expect(result[0].name).toBe('Weight') expect(result[0].value).toBe('') expect(result[1].name).toBe('Color') }) it('merges stored values by id', () => { const values = [ { customField: { id: 'cf-1', name: 'Weight' }, id: 'cfv-1', value: '42' }, ] const result = buildCustomFieldInputs(definitions, values) expect(result[0].value).toBe('42') expect(result[0].customFieldValueId).toBe('cfv-1') }) it('merges stored values by name fallback', () => { const values = [ { name: 'Color', id: 'cfv-2', value: 'Blue' }, ] const result = buildCustomFieldInputs(definitions, values) expect(result[1].value).toBe('Blue') }) it('returns empty array for null structure', () => { expect(buildCustomFieldInputs(null, [])).toEqual([]) }) }) // --------------------------------------------------------------------------- // requiredCustomFieldsFilled // --------------------------------------------------------------------------- describe('requiredCustomFieldsFilled', () => { it('returns true when all required fields are filled', () => { const inputs: CustomFieldInput[] = [ { id: null, name: 'A', type: 'text', required: true, options: [], value: 'hello', customFieldId: null, customFieldValueId: null, orderIndex: 0 }, ] expect(requiredCustomFieldsFilled(inputs)).toBe(true) }) it('returns false when required field is empty', () => { const inputs: CustomFieldInput[] = [ { id: null, name: 'A', type: 'text', required: true, options: [], value: '', customFieldId: null, customFieldValueId: null, orderIndex: 0 }, ] expect(requiredCustomFieldsFilled(inputs)).toBe(false) }) it('returns true for non-required empty field', () => { const inputs: CustomFieldInput[] = [ { id: null, name: 'A', type: 'text', required: false, options: [], value: '', customFieldId: null, customFieldValueId: null, orderIndex: 0 }, ] expect(requiredCustomFieldsFilled(inputs)).toBe(true) }) it('treats boolean "false" as filled', () => { const inputs: CustomFieldInput[] = [ { id: null, name: 'Active', type: 'boolean', required: true, options: [], value: 'false', customFieldId: null, customFieldValueId: null, orderIndex: 0 }, ] expect(requiredCustomFieldsFilled(inputs)).toBe(true) }) it('treats boolean "true" as filled', () => { const inputs: CustomFieldInput[] = [ { id: null, name: 'Active', type: 'boolean', required: true, options: [], value: 'true', customFieldId: null, customFieldValueId: null, orderIndex: 0 }, ] expect(requiredCustomFieldsFilled(inputs)).toBe(true) }) it('treats boolean empty as not filled', () => { const inputs: CustomFieldInput[] = [ { id: null, name: 'Active', type: 'boolean', required: true, options: [], value: '', customFieldId: null, customFieldValueId: null, orderIndex: 0 }, ] expect(requiredCustomFieldsFilled(inputs)).toBe(false) }) it('returns true for empty array', () => { expect(requiredCustomFieldsFilled([])).toBe(true) }) }) // --------------------------------------------------------------------------- // shouldPersistField & formatValueForPersistence // --------------------------------------------------------------------------- describe('shouldPersistField', () => { it('returns true for non-empty text field', () => { expect(shouldPersistField({ value: 'hello' } as CustomFieldInput)).toBe(true) }) it('returns false for empty text field', () => { expect(shouldPersistField({ value: '', type: 'text' } as CustomFieldInput)).toBe(false) }) it('returns true for boolean "true"', () => { expect(shouldPersistField({ value: 'true', type: 'boolean' } as CustomFieldInput)).toBe(true) }) it('returns true for boolean "false"', () => { expect(shouldPersistField({ value: 'false', type: 'boolean' } as CustomFieldInput)).toBe(true) }) it('returns false for boolean empty', () => { expect(shouldPersistField({ value: '', type: 'boolean' } as CustomFieldInput)).toBe(false) }) }) describe('formatValueForPersistence', () => { it('trims text value', () => { expect(formatValueForPersistence({ value: ' hello ', type: 'text' } as CustomFieldInput)).toBe('hello') }) it('returns "true" for boolean true', () => { expect(formatValueForPersistence({ value: 'true', type: 'boolean' } as CustomFieldInput)).toBe('true') }) it('returns "false" for boolean non-true', () => { expect(formatValueForPersistence({ value: 'false', type: 'boolean' } as CustomFieldInput)).toBe('false') expect(formatValueForPersistence({ value: '', type: 'boolean' } as CustomFieldInput)).toBe('false') }) }) // --------------------------------------------------------------------------- // buildCustomFieldMetadata // --------------------------------------------------------------------------- describe('buildCustomFieldMetadata', () => { it('builds metadata from field', () => { const field: CustomFieldInput = { id: null, name: 'Color', type: 'select', required: true, options: ['Red', 'Blue'], value: 'Red', customFieldId: null, customFieldValueId: null, orderIndex: 0, } expect(buildCustomFieldMetadata(field)).toEqual({ customFieldName: 'Color', customFieldType: 'select', customFieldRequired: true, customFieldOptions: ['Red', 'Blue'], }) }) })