import { render, screen, fireEvent } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { Field } from '../Field';
// Mock Mantine components
vi.mock('@mantine/core', async () => {
return {
TextInput: ({ label, description, value, onChange, type, placeholder }) => (
{description &&
{description}
}
),
NumberInput: ({ label, description, value, onChange, placeholder }) => (
onChange(Number(e.target.value))}
placeholder={placeholder}
aria-describedby={description}
/>
{description &&
{description}
}
),
Textarea: ({ label, description, value, onChange, placeholder }) => (
{description &&
{description}
}
),
Switch: ({ label, description, checked, onChange }) => (
{description &&
{description}
}
),
Select: ({ label, description, value, data, onChange, placeholder }) => (
{description &&
{description}
}
),
Text: ({ children, fw, size, c }) => (
{children}
),
};
});
describe('Field', () => {
const mockOnChange = vi.fn();
beforeEach(() => {
vi.clearAllMocks();
});
describe('TextInput (string type)', () => {
it('should render TextInput for string type', () => {
const field = {
id: 'name',
type: 'string',
label: 'Name',
help_text: 'Enter your name',
default: '',
};
render();
expect(screen.getByLabelText('Name')).toBeInTheDocument();
expect(screen.getByText('Enter your name')).toBeInTheDocument();
});
it('should use provided value', () => {
const field = {
id: 'name',
type: 'string',
label: 'Name',
default: '',
};
render();
expect(screen.getByLabelText('Name')).toHaveValue('John');
});
it('should use default value when value is null', () => {
const field = {
id: 'name',
type: 'string',
label: 'Name',
default: 'Default Name',
};
render();
expect(screen.getByLabelText('Name')).toHaveValue('Default Name');
});
it('should call onChange with field id and value', () => {
const field = {
id: 'name',
type: 'string',
label: 'Name',
default: '',
};
render();
fireEvent.change(screen.getByLabelText('Name'), {
target: { value: 'New Value' },
});
expect(mockOnChange).toHaveBeenCalledWith('name', 'New Value');
});
});
describe('NumberInput (number type)', () => {
it('should render NumberInput for number type', () => {
const field = {
id: 'age',
type: 'number',
label: 'Age',
help_text: 'Enter your age',
default: 0,
};
render();
expect(screen.getByLabelText('Age')).toBeInTheDocument();
expect(screen.getByText('Enter your age')).toBeInTheDocument();
});
it('should use provided value', () => {
const field = {
id: 'age',
type: 'number',
label: 'Age',
default: 0,
};
render();
expect(screen.getByLabelText('Age')).toHaveValue(25);
});
it('should default to 0 when value and default are null', () => {
const field = {
id: 'age',
type: 'number',
label: 'Age',
default: null,
};
render();
expect(screen.getByLabelText('Age')).toHaveValue(0);
});
it('should call onChange with field id and numeric value', () => {
const field = {
id: 'age',
type: 'number',
label: 'Age',
default: 0,
};
render();
fireEvent.change(screen.getByLabelText('Age'), {
target: { value: '30' },
});
expect(mockOnChange).toHaveBeenCalledWith('age', 30);
});
});
describe('Switch (boolean type)', () => {
it('should render Switch for boolean type', () => {
const field = {
id: 'active',
type: 'boolean',
label: 'Active',
help_text: 'Toggle active state',
default: false,
};
render();
expect(screen.getByLabelText('Active')).toBeInTheDocument();
expect(screen.getByText('Toggle active state')).toBeInTheDocument();
});
it('should be checked when value is true', () => {
const field = {
id: 'active',
type: 'boolean',
label: 'Active',
default: false,
};
render();
expect(screen.getByLabelText('Active')).toBeChecked();
});
it('should be unchecked when value is false', () => {
const field = {
id: 'active',
type: 'boolean',
label: 'Active',
default: false,
};
render();
expect(screen.getByLabelText('Active')).not.toBeChecked();
});
it('should use default value when value is null', () => {
const field = {
id: 'active',
type: 'boolean',
label: 'Active',
default: true,
};
render();
expect(screen.getByLabelText('Active')).toBeChecked();
});
it('should call onChange with field id and checked state', () => {
const field = {
id: 'active',
type: 'boolean',
label: 'Active',
default: false,
};
render();
fireEvent.click(screen.getByLabelText('Active'));
expect(mockOnChange).toHaveBeenCalledWith('active', true);
});
});
describe('Select (select type)', () => {
it('should render Select for select type', () => {
const field = {
id: 'country',
type: 'select',
label: 'Country',
help_text: 'Select your country',
default: '',
options: [
{ value: 'us', label: 'United States' },
{ value: 'ca', label: 'Canada' },
],
};
render();
expect(screen.getByLabelText('Country')).toBeInTheDocument();
expect(screen.getByText('Select your country')).toBeInTheDocument();
});
it('should render options correctly', () => {
const field = {
id: 'country',
type: 'select',
label: 'Country',
default: '',
options: [
{ value: 'us', label: 'United States' },
{ value: 'ca', label: 'Canada' },
],
};
render();
expect(screen.getByText('United States')).toBeInTheDocument();
expect(screen.getByText('Canada')).toBeInTheDocument();
});
it('should use provided value', () => {
const field = {
id: 'country',
type: 'select',
label: 'Country',
default: '',
options: [
{ value: 'us', label: 'United States' },
{ value: 'ca', label: 'Canada' },
],
};
render();
expect(screen.getByLabelText('Country')).toHaveValue('ca');
});
it('should convert value to string', () => {
const field = {
id: 'status',
type: 'select',
label: 'Status',
default: 1,
options: [
{ value: 1, label: 'Active' },
{ value: 2, label: 'Inactive' },
],
};
render();
expect(screen.getByLabelText('Status')).toHaveValue('1');
});
it('should handle empty options array', () => {
const field = {
id: 'country',
type: 'select',
label: 'Country',
default: '',
options: null,
};
render();
expect(screen.getByLabelText('Country')).toBeInTheDocument();
});
it('should call onChange with field id and selected value', () => {
const field = {
id: 'country',
type: 'select',
label: 'Country',
default: '',
options: [
{ value: 'us', label: 'United States' },
{ value: 'ca', label: 'Canada' },
],
};
render();
fireEvent.change(screen.getByLabelText('Country'), {
target: { value: 'ca' },
});
expect(mockOnChange).toHaveBeenCalledWith('country', 'ca');
});
});
describe('Textarea (text type)', () => {
it('should render Textarea for text type', () => {
const field = {
id: 'bio',
type: 'text',
label: 'Biography',
help_text: 'Enter your biography',
default: '',
};
render();
expect(screen.getByLabelText('Biography')).toBeInTheDocument();
expect(screen.getByText('Enter your biography')).toBeInTheDocument();
});
it('should use provided value', () => {
const field = {
id: 'bio',
type: 'text',
label: 'Biography',
default: '',
};
render();
expect(screen.getByLabelText('Biography')).toHaveValue('My bio');
});
it('should use default value when value is null', () => {
const field = {
id: 'bio',
type: 'text',
label: 'Biography',
default: 'Default bio',
};
render();
expect(screen.getByLabelText('Biography')).toHaveValue('Default bio');
});
it('should call onChange with field id and value', () => {
const field = {
id: 'bio',
type: 'text',
label: 'Biography',
default: '',
};
render();
fireEvent.change(screen.getByLabelText('Biography'), {
target: { value: 'New bio text' },
});
expect(mockOnChange).toHaveBeenCalledWith('bio', 'New bio text');
});
it('should render with placeholder', () => {
const field = {
id: 'bio',
type: 'text',
label: 'Biography',
placeholder: 'Enter your bio here...',
default: '',
};
render();
expect(screen.getByLabelText('Biography')).toHaveAttribute(
'placeholder',
'Enter your bio here...'
);
});
});
describe('Info type', () => {
it('should render info with label and description', () => {
const field = {
id: 'info1',
type: 'info',
label: 'Important Information',
description: 'This is important info',
};
render();
expect(screen.getByText('Important Information')).toBeInTheDocument();
expect(screen.getByText('This is important info')).toBeInTheDocument();
});
it('should render info with only description', () => {
const field = {
id: 'info2',
type: 'info',
description: 'Just a description',
};
render();
expect(screen.getByText('Just a description')).toBeInTheDocument();
});
it('should render info with only label', () => {
const field = {
id: 'info3',
type: 'info',
label: 'Just a label',
};
render();
expect(screen.getByText('Just a label')).toBeInTheDocument();
});
it('should prioritize help_text over description', () => {
const field = {
id: 'info4',
type: 'info',
label: 'Title',
help_text: 'Help text takes priority',
description: 'This should not appear',
};
render();
expect(screen.getByText('Help text takes priority')).toBeInTheDocument();
expect(screen.queryByText('This should not appear')).not.toBeInTheDocument();
});
it('should use field.value if no help_text or description', () => {
const field = {
id: 'info5',
type: 'info',
label: 'Title',
value: 'Value text',
};
render();
expect(screen.getByText('Value text')).toBeInTheDocument();
});
it('should not call onChange for info type', () => {
const field = {
id: 'info6',
type: 'info',
label: 'Read-only Info',
description: 'Cannot be changed',
};
render();
expect(mockOnChange).not.toHaveBeenCalled();
});
});
describe('Password input type', () => {
it('should render password input when input_type is password', () => {
const field = {
id: 'password',
type: 'string',
label: 'Password',
input_type: 'password',
default: '',
};
render();
expect(screen.getByLabelText('Password')).toHaveAttribute('type', 'password');
});
it('should render text input when input_type is not password', () => {
const field = {
id: 'username',
type: 'string',
label: 'Username',
input_type: 'text',
default: '',
};
render();
expect(screen.getByLabelText('Username')).toHaveAttribute('type', 'text');
});
it('should default to text input when input_type is undefined', () => {
const field = {
id: 'email',
type: 'string',
label: 'Email',
default: '',
};
render();
expect(screen.getByLabelText('Email')).toHaveAttribute('type', 'text');
});
});
describe('Description priority', () => {
it('should prioritize help_text over description', () => {
const field = {
id: 'test',
type: 'string',
label: 'Test',
help_text: 'Help text',
description: 'Description text',
default: '',
};
render();
expect(screen.getByText('Help text')).toBeInTheDocument();
expect(screen.queryByText('Description text')).not.toBeInTheDocument();
});
it('should use description when help_text is not provided', () => {
const field = {
id: 'test',
type: 'string',
label: 'Test',
description: 'Description text',
default: '',
};
render();
expect(screen.getByText('Description text')).toBeInTheDocument();
});
it('should use field.value when neither help_text nor description provided', () => {
const field = {
id: 'test',
type: 'string',
label: 'Test',
value: 'Value text',
default: '',
};
render();
expect(screen.getByText('Value text')).toBeInTheDocument();
});
it('should not show description when all are undefined', () => {
const field = {
id: 'test',
type: 'string',
label: 'Test',
default: '',
};
render();
expect(screen.getByLabelText('Test')).toBeInTheDocument();
// No description should be present
});
});
describe('Placeholder handling', () => {
it('should render placeholder for TextInput', () => {
const field = {
id: 'name',
type: 'string',
label: 'Name',
placeholder: 'Enter your name',
default: '',
};
render();
expect(screen.getByLabelText('Name')).toHaveAttribute(
'placeholder',
'Enter your name'
);
});
it('should render placeholder for NumberInput', () => {
const field = {
id: 'age',
type: 'number',
label: 'Age',
placeholder: 'Enter your age',
default: 0,
};
render();
expect(screen.getByLabelText('Age')).toHaveAttribute(
'placeholder',
'Enter your age'
);
});
it('should render placeholder for Select', () => {
const field = {
id: 'country',
type: 'select',
label: 'Country',
placeholder: 'Select a country',
default: '',
options: [
{ value: 'us', label: 'United States' },
{ value: 'ca', label: 'Canada' },
],
};
render();
expect(screen.getByLabelText('Country')).toHaveAttribute(
'placeholder',
'Select a country'
);
});
it('should render placeholder for Textarea', () => {
const field = {
id: 'bio',
type: 'text',
label: 'Biography',
placeholder: 'Tell us about yourself',
default: '',
};
render();
expect(screen.getByLabelText('Biography')).toHaveAttribute(
'placeholder',
'Tell us about yourself'
);
});
});
describe('Edge cases', () => {
it('should handle empty string default value', () => {
const field = {
id: 'test',
type: 'string',
label: 'Test',
default: '',
};
render();
expect(screen.getByLabelText('Test')).toHaveValue('');
});
it('should handle 0 as valid number value', () => {
const field = {
id: 'count',
type: 'number',
label: 'Count',
default: 10,
};
render();
expect(screen.getByLabelText('Count')).toHaveValue(0);
});
it('should handle false as valid boolean value', () => {
const field = {
id: 'enabled',
type: 'boolean',
label: 'Enabled',
default: true,
};
render();
expect(screen.getByLabelText('Enabled')).not.toBeChecked();
});
it('should handle empty string as valid select value', () => {
const field = {
id: 'status',
type: 'select',
label: 'Status',
default: 'active',
options: [
{ value: '', label: 'None' },
{ value: 'active', label: 'Active' },
],
};
render();
expect(screen.getByLabelText('Status')).toHaveValue('');
});
});
describe('Default fallback', () => {
it('should render TextInput for unknown type', () => {
const field = {
id: 'custom',
type: 'unknown',
label: 'Custom Field',
default: '',
};
render();
expect(screen.getByLabelText('Custom Field')).toBeInTheDocument();
});
});
});