Skip to content

Commit

Permalink
feat: added fields (wip)
Browse files Browse the repository at this point in the history
  • Loading branch information
chesterkmr committed Dec 16, 2024
1 parent b631eff commit 7be92c9
Show file tree
Hide file tree
Showing 59 changed files with 1,335 additions and 4,848 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const checkIfDateIsValid = (inputDate: string | Date | number): boolean => {
const date = new Date(inputDate);

if (date.getFullYear() < 1000) return false;

return true;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { describe, expect, it } from 'vitest';
import { checkIfDateIsValid } from './check-if-date-is-valid';

describe('checkIfDateIsValid', () => {
it('should return false for dates before year 1000', () => {
expect(checkIfDateIsValid('0999-12-31')).toBe(false);
expect(checkIfDateIsValid('0001-01-01')).toBe(false);
expect(checkIfDateIsValid(new Date('0500-06-15'))).toBe(false);
});

it('should return true for valid dates after year 1000', () => {
expect(checkIfDateIsValid('2023-01-01')).toBe(true);
expect(checkIfDateIsValid('1000-01-01')).toBe(true);
expect(checkIfDateIsValid('3000-12-31')).toBe(true);
});

it('should handle different input types', () => {
const currentDate = new Date();
expect(checkIfDateIsValid(currentDate)).toBe(true);
expect(checkIfDateIsValid(currentDate.toISOString())).toBe(true);
expect(checkIfDateIsValid(currentDate.getTime())).toBe(true);
});

it('should handle edge cases', () => {
expect(checkIfDateIsValid('1000-01-01')).toBe(true);
expect(checkIfDateIsValid('999-12-31')).toBe(false);
expect(checkIfDateIsValid('9999-12-31')).toBe(true);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './check-if-date-is-valid';
6 changes: 3 additions & 3 deletions packages/ui/src/components/atoms/inputs/Checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as React from 'react';
import { Root, Indicator } from '@radix-ui/react-checkbox';
import { Check } from 'lucide-react';
import { ctw } from '@/common/utils/ctw';
import { Indicator, Root } from '@radix-ui/react-checkbox';
import { Check } from 'lucide-react';
import * as React from 'react';

export const Checkbox = React.forwardRef<
React.ElementRef<typeof Root>,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { render, screen } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import { ErrorsList } from './ErrorsList';

// Mock ErrorMessage component
vi.mock('@/components/atoms', () => ({
ErrorMessage: ({ text, className }: { text: string; className?: string }) => (
<div className={className}>{text}</div>
),
}));

describe('ErrorsList', () => {
it('renders empty list when no errors provided', () => {
render(<ErrorsList errors={[]} />);
expect(screen.queryByRole('listitem')).not.toBeInTheDocument();
});

it('renders list of errors', () => {
const errors = ['Error 1', 'Error 2', 'Error 3'];
render(<ErrorsList errors={errors} />);

errors.forEach(error => {
expect(screen.getByText(error)).toBeInTheDocument();
});
expect(screen.getAllByRole('listitem')).toHaveLength(3);
});

it('applies custom className when provided', () => {
render(<ErrorsList errors={['Error']} className="custom-class" />);
const list = screen.getByRole('list');
expect(list.className).toContain('custom-class');
expect(list.className).toContain('pl-1');
});

it('applies testId to list and list items when provided', () => {
const errors = ['Error 1', 'Error 2'];
render(<ErrorsList errors={errors} testId="test" />);

expect(screen.getByTestId('test-errors-list')).toBeInTheDocument();
expect(screen.getByTestId('test-error-list-item-0')).toBeInTheDocument();
expect(screen.getByTestId('test-error-list-item-1')).toBeInTheDocument();
});

it('renders error type styling by default', () => {
render(<ErrorsList errors={['Error']} />);
// Default error type should not have warning class
expect(screen.getByText('Error').className).not.toContain('text-amber-400');
});

it('renders warning type styling when specified', () => {
render(<ErrorsList errors={['Warning']} type="warning" />);
expect(screen.getByText('Warning').className).toContain('text-amber-400');
});

it('does not apply testId attributes when testId is not provided', () => {
render(<ErrorsList errors={['Error']} />);
expect(screen.queryByTestId(/errors-list/)).not.toBeInTheDocument();
expect(screen.queryByTestId(/error-list-item-/)).not.toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,8 @@ export const AutocompleteInput = ({
//@ts-nocheck
inputProps={{
...params.inputProps,
'data-testid': testId,
className: 'py-0 px-0 h-9',
'data-testid': testId,
}}
onChange={handleInputChange}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ export const DatePickerInput = ({
variant="standard"
fullWidth
size="small"
inputProps={{
...props.inputProps,
className: 'py-0 px-0 h-9',
'data-testid': testId,
}}
onFocus={e => {
setFocused(true);
props.onFocus && props.onFocus(e);
Expand Down Expand Up @@ -143,11 +148,6 @@ export const DatePickerInput = ({
},
disableUnderline: true,
}}
inputProps={{
...props.inputProps,
'data-testid': testId,
className: 'py-0 px-0 h-9',
}}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,35 @@
import { FieldLayout } from '@/pages/CollectionFlowV2/components/ui/field-parts/FieldLayout';
import { IFieldComponentProps } from '@/pages/CollectionFlowV2/types';
import { AutocompleteInput, createTestId } from '@ballerine/ui';
import { FunctionComponent } from 'react';
import { createTestId } from '@/components/organisms/Renderer';
import { AutocompleteInput } from '@ballerine/ui';
import { useField } from '../../hooks/external';
import { FieldLayout } from '../../layouts/FieldLayout';
import { TBaseFormElements, TDynamicFormField } from '../../types';
import { useStack } from '../FieldList/providers/StackProvider';

export interface IAutocompleteFieldOption {
label: string;
value: string;
}

export interface IAutocompleteFieldOptions {
export interface IAutocompleteFieldParams {
placeholder?: string;
options: IAutocompleteFieldOption[];
}

export const AutocompleteField: FunctionComponent<
IFieldComponentProps<string, IAutocompleteFieldOptions>
> = ({ fieldProps, definition, options: _options, stack }) => {
const { value, onChange, onBlur, disabled } = fieldProps;
const { options = [], placeholder = '' } = _options;
export const AutocompleteField: TDynamicFormField<TBaseFormElements, IAutocompleteFieldParams> = ({
element,
}) => {
const { params } = element;
const { stack } = useStack();
const { value, onChange, onBlur, disabled } = useField<string | undefined>(element, stack);
const { options = [], placeholder = '' } = params || {};

return (
<FieldLayout definition={definition} stack={stack}>
<FieldLayout element={element}>
<AutocompleteInput
disabled={disabled}
value={value}
options={options}
testId={createTestId(definition, stack)}
data-testid={createTestId(element, stack)}
placeholder={placeholder}
onChange={event => onChange(event.target.value || '')}
onBlur={onBlur}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { createTestId } from '@/components/organisms/Renderer';
import { cleanup, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { useField } from '../../hooks/external';
import { IFormElement, TBaseFormElements } from '../../types';
import { useStack } from '../FieldList/providers/StackProvider';
import { AutocompleteField, IAutocompleteFieldParams } from './AutocompleteField';

// Mock dependencies
vi.mock('@ballerine/ui', () => ({
AutocompleteInput: ({ children, options, ...props }: any) => (
<input {...props} options={JSON.stringify(options)} type="text" />
),
}));

vi.mock('../../hooks/external', () => ({
useField: vi.fn(),
}));

vi.mock('../FieldList/providers/StackProvider', () => ({
useStack: vi.fn(),
}));

vi.mock('@/components/organisms/Renderer', () => ({
createTestId: vi.fn(),
}));

vi.mock('../../layouts/FieldLayout', () => ({
FieldLayout: ({ children }: any) => <div>{children}</div>,
}));

describe('AutocompleteField', () => {
const mockStack = [0];
const mockOptions = [
{ label: 'Option 1', value: 'opt1' },
{ label: 'Option 2', value: 'opt2' },
];

const mockElement = {
id: 'test-autocomplete',
params: {
placeholder: 'Select an option',
options: mockOptions,
},
} as IFormElement<TBaseFormElements, IAutocompleteFieldParams>;

const mockFieldProps = {
value: '',
onChange: vi.fn(),
onBlur: vi.fn(),
disabled: false,
touched: false,
} as ReturnType<typeof useField>;

beforeEach(() => {
cleanup();
vi.clearAllMocks();
vi.mocked(useStack).mockReturnValue({ stack: mockStack });
vi.mocked(useField).mockReturnValue(mockFieldProps);
vi.mocked(createTestId).mockReturnValue('test-id');
});

it('should render AutocompleteInput component', () => {
render(<AutocompleteField element={mockElement} />);
expect(screen.getByTestId('test-id')).toBeInTheDocument();
});

it('should pass correct props to AutocompleteInput', () => {
render(<AutocompleteField element={mockElement} />);
const input = screen.getByTestId('test-id');

expect(input).toHaveAttribute('placeholder', 'Select an option');
expect(input).not.toBeDisabled();
});

it('should handle value changes', async () => {
const user = userEvent.setup();
render(<AutocompleteField element={mockElement} />);

const input = screen.getByTestId('test-id');
await user.type(input, 'test value');

expect(mockFieldProps.onChange).toHaveBeenCalled();
});

it('should handle blur events', async () => {
const user = userEvent.setup();
render(<AutocompleteField element={mockElement} />);

const input = screen.getByTestId('test-id');
await user.click(input);
await user.tab();

expect(mockFieldProps.onBlur).toHaveBeenCalled();
});

it('should respect disabled state', () => {
vi.mocked(useField).mockReturnValue({
...mockFieldProps,
disabled: true,
});

render(<AutocompleteField element={mockElement} />);
expect(screen.getByTestId('test-id')).toBeDisabled();
});

it('should use default params when none provided', () => {
const elementWithoutParams = {
id: 'test-autocomplete',
} as unknown as IFormElement<TBaseFormElements, IAutocompleteFieldParams>;

render(<AutocompleteField element={elementWithoutParams} />);

const input = screen.getByTestId('test-id');
expect(input).toHaveAttribute('placeholder', '');
});

it('should pass options from element params to AutocompleteInput', () => {
const elementWithOptions = {
...mockElement,
params: {
options: mockOptions,
placeholder: 'Select an option',
},
};

render(<AutocompleteField element={elementWithOptions} />);

expect(screen.getByTestId('test-id')).toHaveAttribute('options', JSON.stringify(mockOptions));
});
});
Original file line number Diff line number Diff line change
@@ -1,35 +1,11 @@
import { FieldLayout } from '@/pages/CollectionFlowV2/components/ui/field-parts/FieldLayout';
import { IFieldComponentProps } from '@/pages/CollectionFlowV2/types';
import { Checkbox, createTestId } from '@ballerine/ui';
import { FunctionComponent } from 'react';
import { Checkbox } from '@/components/atoms';
import { useField } from '../../hooks/external';
import { TDynamicFormField } from '../../types';
import { useStack } from '../FieldList/providers/StackProvider';

export interface ICheckboxFieldOptions {
label: string;
}
export const CheckboxField: TDynamicFormField = ({ element }) => {
const { stack } = useStack();
const { value, onChange, disabled } = useField<boolean | undefined>(element, stack);

export const CheckboxField: FunctionComponent<
IFieldComponentProps<boolean, ICheckboxFieldOptions>
> = ({ fieldProps, definition, options, stack }) => {
const { value, onChange, onBlur, disabled } = fieldProps;
const { label } = options;

return (
<FieldLayout
definition={definition}
stack={stack}
className="flex-row flex-row-reverse items-center justify-end"
>
<Checkbox
className="border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground bg-white"
color="primary"
checked={value}
disabled={disabled}
data-testid={createTestId(definition, stack)}
onCheckedChange={e => {
onChange(Boolean(e));
}}
onBlur={onBlur}
/>
</FieldLayout>
);
return <Checkbox checked={Boolean(value)} onChange={onChange} disabled={disabled} />;
};
Loading

0 comments on commit 7be92c9

Please sign in to comment.