Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/core/src/sugar/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
ValidationStage,
FailFn,
} from './useValidation';
import { useTransform, SugarUseTransform } from './useTransform';
import { useIsPending, SugarUseIsPending } from './useIsPending';

export class SugarInner<T extends SugarValue> {
Expand Down Expand Up @@ -290,6 +291,11 @@ export class SugarInner<T extends SugarValue> {
) =>
useValidation(this as Sugar<T>, validator, deps)) as SugarUseValidation<T>;

useTransform: SugarUseTransform<T> = (<U extends SugarValue>(config: {
forward: (value: T) => Promise<U>;
backward: (value: U) => Promise<T>;
}) => useTransform(this as Sugar<T>, config)) as SugarUseTransform<T>;

useIsPending: SugarUseIsPending = (() =>
useIsPending(this as Sugar<T>)) as SugarUseIsPending;
}
2 changes: 2 additions & 0 deletions packages/core/src/sugar/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export type SugarTemplateSetter<T extends SugarValue> = (

import type { SugarUseObject } from './useObject';
import type { SugarUseValidation } from './useValidation';
import type { SugarUseTransform } from './useTransform';
import type { SugarUseIsPending } from './useIsPending';

type SugarType<T extends SugarValue> = {
Expand All @@ -53,6 +54,7 @@ type SugarType<T extends SugarValue> = {
destroy: () => void;
useObject: SugarUseObject<T>;
useValidation: SugarUseValidation<T>;
useTransform: SugarUseTransform<T>;
useIsPending: SugarUseIsPending;
addEventListener: <K extends keyof SugarEvent>(
type: K,
Expand Down
72 changes: 72 additions & 0 deletions packages/core/src/sugar/useTransform.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { useEffect, useRef } from 'react';
import { Sugar, SugarGetResult, SugarValue } from './types';
import { SugarInner } from '.';

export type SugarTransformConfig<T extends SugarValue, U extends SugarValue> = {
forward: (value: T) => Promise<U>;
backward: (value: U) => Promise<T>;
};

export type SugarUseTransform<T extends SugarValue> = <U extends SugarValue>(
config: SugarTransformConfig<T, U>
) => Sugar<U>;

export function useTransform<T extends SugarValue, U extends SugarValue>(
sugar: Sugar<T>,
config: SugarTransformConfig<T, U>
): Sugar<U> {
const transformedSugar = useRef<Sugar<U>>(undefined);

if (!transformedSugar.current) {
transformedSugar.current = new SugarInner<U>({ status: 'pending' });
}

useEffect(() => {
transformedSugar.current!.addEventListener('change', () =>
sugar.dispatchEvent('change')

Check warning on line 26 in packages/core/src/sugar/useTransform.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/sugar/useTransform.ts#L26

Added line #L26 was not covered by tests
);
transformedSugar.current!.addEventListener('blur', () =>
sugar.dispatchEvent('blur')

Check warning on line 29 in packages/core/src/sugar/useTransform.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/sugar/useTransform.ts#L29

Added line #L29 was not covered by tests
);

sugar.ready(
async (submit) => {
const transformedResult = await transformedSugar.current!.get(submit);
Comment thread
AsPulse marked this conversation as resolved.
if (transformedResult.result !== 'success') {
return transformedResult as SugarGetResult<T>;

Check warning on line 36 in packages/core/src/sugar/useTransform.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/sugar/useTransform.ts#L36

Added line #L36 was not covered by tests
}
const originalValue = await config.backward(transformedResult.value);
return {
result: 'success',
value: originalValue,
};
},
async (value) => {
const transformedValue = await config.forward(value);
return await transformedSugar.current!.set(transformedValue);

Check warning on line 46 in packages/core/src/sugar/useTransform.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/sugar/useTransform.ts#L44-L46

Added lines #L44 - L46 were not covered by tests
},
async (value, executeSet = true) => {
const transformedValue = await config.forward(value);
return await transformedSugar.current!.setTemplate(

Check warning on line 50 in packages/core/src/sugar/useTransform.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/sugar/useTransform.ts#L49-L50

Added lines #L49 - L50 were not covered by tests
transformedValue,
executeSet
);
}
);

const originalTemplate = (sugar as SugarInner<T>).template;
if (originalTemplate?.status === 'pending') {
(transformedSugar.current as SugarInner<U>).setPendingTemplate();

Check warning on line 59 in packages/core/src/sugar/useTransform.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/sugar/useTransform.ts#L59

Added line #L59 was not covered by tests
} else if (originalTemplate?.status === 'resolved') {
config.forward(originalTemplate.value).then((transformedValue) => {
transformedSugar.current!.setTemplate(transformedValue, false);

Check warning on line 62 in packages/core/src/sugar/useTransform.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/sugar/useTransform.ts#L61-L62

Added lines #L61 - L62 were not covered by tests
});
}

return () => {
transformedSugar.current!.destroy();
};
}, [sugar, config]);

return transformedSugar.current!;
}
52 changes: 52 additions & 0 deletions tests/core-unittest/src/useTransform-simple.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { useForm, TextInput } from '@sugarform/core';
import { render, renderHook } from '@testing-library/react';
import { expect, test } from 'vitest';

test('useTransform basic functionality', async () => {
const { result: form } = renderHook(() =>
useForm<string | null>({ template: null })
);

const { result: transformedSugar } = renderHook(() =>
form.current.sugar.useTransform<string>({
forward: async (value: string | null) => value ?? '',
backward: async (value: string) => (value === '' ? null : value),
})
);

render(<TextInput sugar={transformedSugar.current} placeholder="input" />);

await expect(form.current.sugar.get()).resolves.toStrictEqual({
result: 'success',
value: null,
});

await expect(transformedSugar.current.get()).resolves.toStrictEqual({
result: 'success',
value: '',
});

await transformedSugar.current.set('test');

await expect(form.current.sugar.get()).resolves.toStrictEqual({
result: 'success',
value: 'test',
});

await expect(transformedSugar.current.get()).resolves.toStrictEqual({
result: 'success',
value: 'test',
});

await transformedSugar.current.set('');

await expect(form.current.sugar.get()).resolves.toStrictEqual({
result: 'success',
value: null,
});

await expect(transformedSugar.current.get()).resolves.toStrictEqual({
result: 'success',
value: '',
});
});