Skip to content

Commit

Permalink
feat: finalized documentfield
Browse files Browse the repository at this point in the history
  • Loading branch information
chesterkmr committed Jan 16, 2025
1 parent 4f16a81 commit cd9d588
Show file tree
Hide file tree
Showing 19 changed files with 179 additions and 81 deletions.
9 changes: 9 additions & 0 deletions packages/ui/src/common/utils/async-compose/async-compose.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const asyncCompose = <T>(...fns: Array<(arg: T) => Promise<T> | T>) => {
return async (initialValue: T): Promise<T> => {
return fns.reduceRight(async (promise: Promise<T>, fn) => {
const value = await promise;

return Promise.resolve(fn(value));
}, Promise.resolve(initialValue));
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { describe, expect, it } from 'vitest';
import { asyncCompose } from './async-compose';

describe('asyncCompose', () => {
it('should compose async functions from right to left', async () => {
const addOne = async (x: number) => x + 1;
const multiplyByTwo = async (x: number) => x * 2;
const subtractThree = async (x: number) => x - 3;

const composed = asyncCompose(subtractThree, multiplyByTwo, addOne);
const result = await composed(5);

// ((5 + 1) * 2) - 3 = 9
expect(result).toBe(9);
});

it('should work with mix of sync and async functions', async () => {
const addOne = (x: number) => x + 1;
const multiplyByTwo = async (x: number) => x * 2;
const subtractThree = (x: number) => x - 3;

const composed = asyncCompose(subtractThree, multiplyByTwo, addOne);
const result = await composed(5);

expect(result).toBe(9);
});

it('should handle single function', async () => {
const addOne = async (x: number) => x + 1;

const composed = asyncCompose(addOne);
const result = await composed(5);

expect(result).toBe(6);
});

it('should handle empty function array', async () => {
const composed = asyncCompose();
const result = await composed(5);

expect(result).toBe(5);
});

it('should maintain function execution order', async () => {
const executionOrder: number[] = [];

const fn1 = async (x: number) => {
executionOrder.push(1);

return x;
};
const fn2 = async (x: number) => {
executionOrder.push(2);

return x;
};
const fn3 = async (x: number) => {
executionOrder.push(3);

return x;
};

const composed = asyncCompose(fn1, fn2, fn3);
await composed(5);

expect(executionOrder).toEqual([3, 2, 1]);
});
});
1 change: 1 addition & 0 deletions packages/ui/src/common/utils/async-compose/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './async-compose';
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const DynamicFormV2 = forwardRef(
onFieldChange,
});
const touchedApi = useTouched(elements, valuesApi.values);
const fieldHelpers = useFieldHelpers({ valuesApi, touchedApi });
const fieldHelpers = useFieldHelpers<TValues>({ valuesApi, touchedApi });
const { submit } = useSubmit({ values: valuesApi.values, onSubmit });

useImperativeHandle(ref, () => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export interface IDynamicFormContext<TValues extends object> {
values: TValues;
touched: ITouchedState;
elementsMap: TElementsMap;
fieldHelpers: IFieldHelpers;
fieldHelpers: IFieldHelpers<TValues>;
submit: () => void;
callbacks: IDynamicFormCallbacks;
metadata: Record<string, string>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const SubmitButton: TDynamicFormElement<string, ISubmitButtonParams> = ({
const { id } = useElement(element);
const { stack } = useStack();
const { disabled: _disabled, onClick } = useControl(element, stack);
const { fieldHelpers, submit } = useDynamicForm();
const { fieldHelpers, values, submit } = useDynamicForm();
const { runTasks, isRunning } = useTaskRunner();
const { sendEvent } = useEvents(element);

Expand Down Expand Up @@ -47,13 +47,15 @@ export const SubmitButton: TDynamicFormElement<string, ISubmitButtonParams> = ({
}

console.log('Starting tasks');
await runTasks();
const updatedContext = await runTasks({ ...values });
console.log('Tasks finished');

fieldHelpers.setValues(updatedContext);

submit();

sendEvent('onSubmit');
}, [submit, isValid, touchAllFields, runTasks, sendEvent, errors, onClick]);
}, [submit, isValid, touchAllFields, runTasks, sendEvent, errors, onClick, values, fieldHelpers]);

return (
<Button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { useValidator } from '../../../Validator';
import { IValidatorContext } from '../../../Validator/context';
import { IDynamicFormContext, useDynamicForm } from '../../context';
import { useControl } from '../../hooks/external/useControl/useControl';
import { useElement } from '../../hooks/external/useElement';
import { useField } from '../../hooks/external/useField';
import { useEvents } from '../../hooks/internal/useEvents';
Expand All @@ -22,7 +23,7 @@ vi.mock('../../hooks/external/useElement');
vi.mock('../../hooks/external/useField');
vi.mock('../../hooks/internal/useEvents');
vi.mock('../../providers/TaskRunner/hooks/useTaskRunner');

vi.mock('../../hooks/external/useControl/useControl');
describe('SubmitButton', () => {
const mockElement = {
id: 'test-button',
Expand All @@ -38,6 +39,7 @@ describe('SubmitButton', () => {
getValue: vi.fn(),
setTouched: vi.fn(),
setValue: vi.fn(),
setValues: vi.fn(),
};

const mockSendEvent = vi.fn();
Expand Down Expand Up @@ -83,6 +85,12 @@ describe('SubmitButton', () => {
sendEvent: mockSendEvent,
sendEventAsync: vi.fn(),
} as unknown as ReturnType<typeof useEvents>);
vi.mocked(useControl).mockReturnValue({
disabled: false,
onClick: vi.fn(),
onFocus: vi.fn(),
onBlur: vi.fn(),
});
});

afterEach(() => {
Expand Down Expand Up @@ -157,6 +165,7 @@ describe('SubmitButton', () => {
it('does not submit or trigger events when form is invalid', async () => {
const mockSubmit = vi.fn();
const mockRunTasks = vi.fn();
const mockOnClick = vi.fn();

vi.mocked(useDynamicForm).mockReturnValue({
validationParams: { validateOnBlur: false },
Expand All @@ -181,11 +190,18 @@ describe('SubmitButton', () => {
values: {},
validate: vi.fn(),
} as unknown as IValidatorContext<object>);
vi.mocked(useControl).mockReturnValue({
disabled: false,
onClick: mockOnClick,
onFocus: vi.fn(),
onBlur: vi.fn(),
});

render(<SubmitButton element={mockElement} />);

await userEvent.click(screen.getByTestId('test-button-submit-button'));

expect(mockOnClick).toHaveBeenCalled();
expect(mockFieldHelpers.touchAllFields).toHaveBeenCalled();
expect(mockRunTasks).not.toHaveBeenCalled();
expect(mockSubmit).not.toHaveBeenCalled();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import { Input } from '@/components/atoms/Input';
import { createTestId } from '@/components/organisms/Renderer/utils/create-test-id';
import { Upload, XCircle } from 'lucide-react';
import { useCallback, useMemo, useRef } from 'react';
import { useField } from '../../hooks/external';
import { useElement, useField } from '../../hooks/external';
import { useMountEvent } from '../../hooks/internal/useMountEvent';
import { useUnmountEvent } from '../../hooks/internal/useUnmountEvent';
import { FieldDescription } from '../../layouts/FieldDescription';
import { FieldErrors } from '../../layouts/FieldErrors';
import { FieldLayout } from '../../layouts/FieldLayout';
import { FieldPriorityReason } from '../../layouts/FieldPriorityReason';
import { useTaskRunner } from '../../providers/TaskRunner/hooks/useTaskRunner';
import { IFormElement, TDynamicFormField } from '../../types';
import { useStack } from '../FieldList/providers/StackProvider';
import { IFileFieldParams } from '../FileField';
Expand Down Expand Up @@ -39,8 +40,10 @@ export const DocumentField: TDynamicFormField<IDocumentFieldParams> = ({ element

const { params } = element;
const { placeholder = 'Choose file', acceptFileFormats = undefined } = params || {};
const { removeTask } = useTaskRunner();

const { stack } = useStack();
const { id } = useElement(element, stack);
const {
value: documentsList,
disabled,
Expand Down Expand Up @@ -84,11 +87,12 @@ export const DocumentField: TDynamicFormField<IDocumentFieldParams> = ({ element
);

onChange(updatedDocuments);
removeTask(id);

if (inputRef.current) {
inputRef.current.value = '';
}
}, [documentsList, element, onChange]);
}, [documentsList, element, onChange, id, removeTask]);

return (
<FieldLayout element={element}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export const composePathToFileId = (
documentIndex: number,
pageProperty: string,
pageIndex: number,
pageProperty = 'ballerineFileId',
pageIndex = 0,
) => `[${documentIndex}].pages[${pageIndex}].${pageProperty}`;
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { AnyObject } from '@/common';
import get from 'lodash/get';
import set from 'lodash/set';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useDynamicForm } from '../../../../context';
import { uploadFile } from '../../../../helpers/upload-file';
Expand Down Expand Up @@ -80,25 +82,32 @@ export const useDocumentUpload = (
element,
e.target?.files?.[0] as File,
);

onChange(updatedDocuments);

const taskRun = async () => {
const taskRun = async (context: AnyObject) => {
try {
const documents = get(context, element.valueDestination);

setIsUploading(true);
const result = await uploadFile(
e.target?.files?.[0] as File,
uploadParams as IDocumentFieldParams['uploadSettings'],
);

const documents = get(valuesRef.current, element.valueDestination);
const updatedDocuments = createOrUpdateFileIdOrFileInDocuments(
documents,
element,
result,
);
onChange(updatedDocuments);

set(context, element.valueDestination, updatedDocuments);

return context;
} catch (error) {
console.error('Failed to upload file.', error);
console.error('Failed to upload file.', error, element);

return context;
} finally {
setIsUploading(false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ export const useFieldList = ({ element }: IUseFieldListProps) => {
if (!Array.isArray(value)) return;

const newValue = value.filter((_, i) => i !== index);
console.log('newValue', newValue);
onChange(newValue);
},
[value, onChange],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { AnyObject } from '@/common';
import set from 'lodash/set';
import { useCallback, useState } from 'react';
import { useDynamicForm } from '../../../../context';
import { useElement, useField } from '../../../../hooks/external';
Expand Down Expand Up @@ -61,16 +63,20 @@ export const useFileUpload = (
if (uploadOn === 'submit') {
onChange(e.target?.files?.[0] as File);

const taskRun = async () => {
const taskRun = async (context: AnyObject) => {
try {
setIsUploading(true);
const result = await uploadFile(
e.target?.files?.[0] as File,
uploadParams as IFileFieldParams['uploadSettings'],
);
onChange(result);
set(context, element.valueDestination, result);

return context;
} catch (error) {
console.error('Failed to upload file.', error);

return context;
} finally {
setIsUploading(false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export interface IFieldHelpers {
setTouched: (fieldId: string, touched: boolean) => void;
setValue: <T>(fieldId: string, valueDestination: string, value: T) => void;
touchAllFields: () => void;
setValues: (values: any) => void;
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const useFieldHelpers = <TValues extends object>({
valuesApi,
touchedApi,
}: IUseFieldHelpersParams<TValues>) => {
const { values, setFieldValue } = valuesApi;
const { values, setFieldValue, setValues } = valuesApi;
const { touched, setFieldTouched, touchAllFields } = touchedApi;

const getTouched = useCallback(
Expand All @@ -35,9 +35,10 @@ export const useFieldHelpers = <TValues extends object>({
getValue,
setTouched: setFieldTouched,
setValue: setFieldValue,
setValues: setValues,
touchAllFields: touchAllFields,
}),
[getTouched, getValue, setFieldTouched, setFieldValue, touchAllFields],
[getTouched, getValue, setFieldTouched, setFieldValue, setValues, touchAllFields],
);

return helpers;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ describe('useFieldHelpers', () => {
const mockSetFieldValue = vi.fn();
const mockSetFieldTouched = vi.fn();
const mockTouchAllFields = vi.fn();
const mockSetValues = vi.fn();

const mockValuesApi = {
values: {
Expand All @@ -26,6 +27,7 @@ describe('useFieldHelpers', () => {
},
},
setFieldValue: mockSetFieldValue,
setValues: mockSetValues,
};

const mockTouchedApi = {
Expand Down Expand Up @@ -57,6 +59,7 @@ describe('useFieldHelpers', () => {
expect(result.current).toHaveProperty('getValue');
expect(result.current).toHaveProperty('setTouched');
expect(result.current).toHaveProperty('setValue');
expect(result.current).toHaveProperty('setValues');
expect(result.current).toHaveProperty('touchAllFields');
});

Expand Down Expand Up @@ -98,6 +101,16 @@ describe('useFieldHelpers', () => {
expect(mockSetFieldValue).toHaveBeenCalledWith('field1', 'path.to.field', 'newValue');
});

it('setValues should call valuesApi.setValues', () => {
const { result } = setup();

const newValues = { field1: 'new1', field2: 'new2' };
result.current.setValues(newValues);

expect(mockSetValues).toHaveBeenCalledTimes(1);
expect(mockSetValues).toHaveBeenCalledWith(newValues);
});

it('touchAllFields should call touchedApi.touchAllFields', () => {
const { result } = setup();

Expand Down
Loading

0 comments on commit cd9d588

Please sign in to comment.