This commit is contained in:
Stefan Pejcic
2024-11-07 19:03:37 +01:00
parent c6df945ed5
commit 09f9f9502d
2472 changed files with 620417 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
export * from "./useSelect";
export * from "./useCheckboxGroup";
export * from "./useRadioGroup";

View File

@@ -0,0 +1,134 @@
import { renderHook, waitFor } from "@testing-library/react";
import { TestWrapper } from "@test";
import { useCheckboxGroup } from "./";
describe("render hook default options", () => {
it("should success data with resource", async () => {
const { result } = renderHook(
() =>
useCheckboxGroup({
resource: "posts",
}),
{
wrapper: TestWrapper({}),
},
);
await waitFor(() => {
expect(result.current.queryResult.isSuccess).toBeTruthy();
});
await waitFor(() =>
expect(result.current.checkboxGroupProps.options).toHaveLength(2),
);
await waitFor(() =>
expect(result.current.checkboxGroupProps.options).toEqual([
{
label:
"Necessitatibus necessitatibus id et cupiditate provident est qui amet.",
value: "1",
},
{ label: "Recusandae consectetur aut atque est.", value: "2" },
]),
);
});
it("should success data with resource with optionLabel and optionValue", async () => {
const { result } = renderHook(
() =>
useCheckboxGroup<{ id: string; slug: string }>({
resource: "posts",
optionLabel: "slug",
optionValue: "id",
}),
{
wrapper: TestWrapper({}),
},
);
await waitFor(() => {
expect(result.current.queryResult.isSuccess).toBeTruthy();
});
await waitFor(() =>
expect(result.current.checkboxGroupProps.options).toHaveLength(2),
);
await waitFor(() =>
expect(result.current.checkboxGroupProps.options).toEqual([
{ label: "ut-ad-et", value: "1" },
{ label: "consequatur-molestiae-rerum", value: "2" },
]),
);
});
it("should generate options with custom optionLabel and optionValue functions", async () => {
const { result } = renderHook(
() =>
useCheckboxGroup({
resource: "posts",
optionLabel: (item) => `${item.title} - ${item.userId}`,
optionValue: (item) => `${item.id}`,
}),
{
wrapper: TestWrapper({}),
},
);
await waitFor(() => {
expect(result.current.queryResult.isSuccess).toBeTruthy();
});
const { checkboxGroupProps } = result.current;
expect(checkboxGroupProps.options).toHaveLength(2);
expect(checkboxGroupProps.options).toEqual([
{
label:
"Necessitatibus necessitatibus id et cupiditate provident est qui amet. - 5",
value: "1",
},
{ label: "Recusandae consectetur aut atque est. - 36", value: "2" },
]);
});
it("should invoke queryOptions methods successfully", async () => {
const mockFunc = jest.fn();
const { result } = renderHook(
() =>
useCheckboxGroup<{ id: string; slug: string }>({
resource: "posts",
optionLabel: "slug",
optionValue: "id",
queryOptions: {
onSuccess: (data) => {
mockFunc();
},
},
}),
{
wrapper: TestWrapper({}),
},
);
await waitFor(() => {
expect(result.current.queryResult.isSuccess).toBeTruthy();
});
await waitFor(() =>
expect(result.current.checkboxGroupProps.options).toHaveLength(2),
);
await waitFor(() =>
expect(result.current.checkboxGroupProps.options).toEqual([
{ label: "ut-ad-et", value: "1" },
{ label: "consequatur-molestiae-rerum", value: "2" },
]),
);
expect(mockFunc).toBeCalled();
});
});

View File

@@ -0,0 +1,108 @@
import type { QueryObserverResult } from "@tanstack/react-query";
import type { Checkbox } from "antd";
import {
type BaseRecord,
type GetListResponse,
type HttpError,
type UseSelectProps,
useSelect,
type BaseKey,
pickNotDeprecated,
type BaseOption,
} from "@refinedev/core";
export type UseCheckboxGroupReturnType<
TData extends BaseRecord = BaseRecord,
TOption extends BaseOption = BaseOption,
> = {
checkboxGroupProps: Omit<
React.ComponentProps<typeof Checkbox.Group>,
"options"
> & {
options: TOption[];
};
query: QueryObserverResult<GetListResponse<TData>>;
/**
* @deprecated Use `query` instead
*/
queryResult: QueryObserverResult<GetListResponse<TData>>;
};
type UseCheckboxGroupProps<TQueryFnData, TError, TData> = Omit<
UseSelectProps<TQueryFnData, TError, TData>,
"defaultValue"
> & {
/**
* Sets the default value
*/
defaultValue?: BaseKey[];
};
/**
* `useCheckboxGroup` hook allows you to manage an Ant Design {@link https://ant.design/components/checkbox/#components-checkbox-demo-group Checkbox.Group} component when records in a resource needs to be used as checkbox options.
*
* @see {@link https://refine.dev/docs/api-reference/antd/hooks/field/useCheckboxGroup/} for more details
*
* @typeParam TQueryFnData - Result data returned by the query function. Extends {@link https://refine.dev/docs/api-reference/core/interfaceReferences#baserecord `BaseRecord`}
* @typeParam TError - Custom error object that extends {@link https://refine.dev/docs/api-reference/core/interfaceReferences#httperror `HttpError`}
* @typeParam TData - Result data returned by the `select` function. Extends {@link https://refine.dev/docs/api-reference/core/interfaceReferences#baserecord `BaseRecord`}. Defaults to `TQueryFnData`
*
*/
export const useCheckboxGroup = <
TQueryFnData extends BaseRecord = BaseRecord,
TError extends HttpError = HttpError,
TData extends BaseRecord = TQueryFnData,
TOption extends BaseOption = BaseOption,
>({
resource,
sort,
sorters,
filters,
optionLabel,
optionValue,
queryOptions,
fetchSize,
pagination,
liveMode,
defaultValue,
selectedOptionsOrder,
onLiveEvent,
liveParams,
meta,
metaData,
dataProviderName,
}: UseCheckboxGroupProps<
TQueryFnData,
TError,
TData
>): UseCheckboxGroupReturnType<TData, TOption> => {
const { query, options } = useSelect<TQueryFnData, TError, TData, TOption>({
resource,
sort,
sorters,
filters,
optionLabel,
optionValue,
queryOptions,
fetchSize,
pagination,
liveMode,
defaultValue,
selectedOptionsOrder,
onLiveEvent,
liveParams,
meta: pickNotDeprecated(meta, metaData),
metaData: pickNotDeprecated(meta, metaData),
dataProviderName,
});
return {
checkboxGroupProps: {
options,
defaultValue,
},
query,
queryResult: query,
};
};

View File

@@ -0,0 +1,151 @@
import { renderHook, waitFor } from "@testing-library/react";
import { TestWrapper } from "@test";
import { useRadioGroup } from "./";
describe("render hook default options", () => {
it("should success data without default values", async () => {
const { result } = renderHook(
() =>
useRadioGroup({
resource: "posts",
}),
{
wrapper: TestWrapper({}),
},
);
await waitFor(() => {
expect(result.current.queryResult.isSuccess).toBeTruthy();
});
await waitFor(() =>
expect(result.current.radioGroupProps.options).toHaveLength(2),
);
await waitFor(() =>
expect(result.current.radioGroupProps.options).toEqual([
{
label:
"Necessitatibus necessitatibus id et cupiditate provident est qui amet.",
value: "1",
},
{ label: "Recusandae consectetur aut atque est.", value: "2" },
]),
);
});
it("should success data with resource with optionLabel and optionValue", async () => {
const { result } = renderHook(
() =>
useRadioGroup<{ id: string; slug: string }>({
resource: "posts",
optionLabel: "slug",
optionValue: "id",
}),
{
wrapper: TestWrapper({}),
},
);
await waitFor(() => {
expect(result.current.queryResult.isSuccess).toBeTruthy();
});
await waitFor(() =>
expect(result.current.radioGroupProps.options).toHaveLength(2),
);
await waitFor(() =>
expect(result.current.radioGroupProps.options).toEqual([
{ label: "ut-ad-et", value: "1" },
{ label: "consequatur-molestiae-rerum", value: "2" },
]),
);
});
it("should generate options with custom optionLabel and optionValue functions", async () => {
const { result } = renderHook(
() =>
useRadioGroup({
resource: "posts",
optionLabel: (item) => `${item.title} - ${item.userId}`,
optionValue: (item) => `${item.id}`,
}),
{
wrapper: TestWrapper({}),
},
);
await waitFor(() => {
expect(result.current.queryResult.isSuccess).toBeTruthy();
});
const { radioGroupProps } = result.current;
expect(radioGroupProps.options).toHaveLength(2);
expect(radioGroupProps.options).toEqual([
{
label:
"Necessitatibus necessitatibus id et cupiditate provident est qui amet. - 5",
value: "1",
},
{ label: "Recusandae consectetur aut atque est. - 36", value: "2" },
]);
});
it("should invoke queryOptions methods successfully", async () => {
const mockFunc = jest.fn();
const { result } = renderHook(
() =>
useRadioGroup<{ id: string; slug: string }>({
resource: "posts",
optionLabel: "slug",
optionValue: "id",
queryOptions: {
onSuccess: (data) => {
mockFunc();
},
},
}),
{
wrapper: TestWrapper({}),
},
);
await waitFor(() => {
expect(result.current.queryResult.isSuccess).toBeTruthy();
});
await waitFor(() =>
expect(result.current.radioGroupProps.options).toHaveLength(2),
);
await waitFor(() =>
expect(result.current.radioGroupProps.options).toEqual([
{ label: "ut-ad-et", value: "1" },
{ label: "consequatur-molestiae-rerum", value: "2" },
]),
);
expect(mockFunc).toBeCalled();
});
it("should work with queryResult and query", async () => {
const { result } = renderHook(
() =>
useRadioGroup({
resource: "posts",
}),
{
wrapper: TestWrapper({}),
},
);
await waitFor(() => {
expect(result.current.queryResult.isSuccess).toBeTruthy();
});
expect(result.current.query).toEqual(result.current.queryResult);
});
});

View File

@@ -0,0 +1,105 @@
import type { QueryObserverResult } from "@tanstack/react-query";
import type { Radio } from "antd";
import {
type BaseKey,
type BaseOption,
type BaseRecord,
type GetListResponse,
type HttpError,
pickNotDeprecated,
useSelect,
type UseSelectProps,
} from "@refinedev/core";
export type UseRadioGroupReturnType<
TData extends BaseRecord = BaseRecord,
TOption extends BaseOption = BaseOption,
> = {
radioGroupProps: Omit<React.ComponentProps<typeof Radio.Group>, "options"> & {
options: TOption[];
};
query: QueryObserverResult<GetListResponse<TData>>;
/**
* @deprecated Use `query` instead
*/
queryResult: QueryObserverResult<GetListResponse<TData>>;
};
type UseRadioGroupProps<TQueryFnData, TError, TData> = Omit<
UseSelectProps<TQueryFnData, TError, TData>,
"defaultValue"
> & {
/**
* Sets the default value
*/
defaultValue?: BaseKey;
};
/**
* `useRadioGroup` hook allows you to manage an Ant Design {@link https://ant.design/components/radio/#components-radio-demo-radiogroup-with-name Radio.Group} component when records in a resource needs to be used as radio options.
*
* @see {@link https://refine.dev/docs/api-reference/antd/hooks/field/useRadioGroup/} for more details.
*
* @typeParam TQueryFnData - Result data returned by the query function. Extends {@link https://refine.dev/docs/api-reference/core/interfaceReferences#baserecord `BaseRecord`}
* @typeParam TError - Custom error object that extends {@link https://refine.dev/docs/api-reference/core/interfaceReferences#httperror `HttpError`}
* @typeParam TData - Result data returned by the `select` function. Extends {@link https://refine.dev/docs/api-reference/core/interfaceReferences#baserecord `BaseRecord`}. Defaults to `TQueryFnData`
*
*/
export const useRadioGroup = <
TQueryFnData extends BaseRecord = BaseRecord,
TError extends HttpError = HttpError,
TData extends BaseRecord = TQueryFnData,
TOption extends BaseOption = BaseOption,
>({
resource,
sort,
sorters,
filters,
optionLabel,
optionValue,
queryOptions,
fetchSize,
pagination,
liveMode,
defaultValue,
selectedOptionsOrder,
onLiveEvent,
liveParams,
meta,
metaData,
dataProviderName,
}: UseRadioGroupProps<TQueryFnData, TError, TData>): UseRadioGroupReturnType<
TData,
TOption
> => {
const { query, options } = useSelect<TQueryFnData, TError, TData, TOption>({
resource,
sort,
sorters,
filters,
optionLabel,
optionValue,
queryOptions,
fetchSize,
pagination,
liveMode,
defaultValue,
selectedOptionsOrder,
onLiveEvent,
liveParams,
meta: pickNotDeprecated(meta, metaData),
metaData: pickNotDeprecated(meta, metaData),
dataProviderName,
});
return {
radioGroupProps: {
options,
defaultValue,
},
query,
queryResult: query,
};
};

View File

@@ -0,0 +1,372 @@
import { renderHook, waitFor } from "@testing-library/react";
import { MockJSONServer, TestWrapper } from "@test";
import { useSelect } from "./";
describe("useSelect Hook", () => {
it("default", async () => {
const { result } = renderHook(
() =>
useSelect({
resource: "posts",
}),
{
wrapper: TestWrapper({}),
},
);
await waitFor(() => {
expect(!result.current.queryResult.isFetching).toBeTruthy();
});
await waitFor(() =>
expect(result.current.selectProps.options).toHaveLength(2),
);
await waitFor(() =>
expect(result.current.selectProps.options).toEqual([
{
label:
"Necessitatibus necessitatibus id et cupiditate provident est qui amet.",
value: "1",
},
{ label: "Recusandae consectetur aut atque est.", value: "2" },
]),
);
});
it("defaultValue", async () => {
const { result } = renderHook(
() =>
useSelect({
resource: "posts",
defaultValue: ["1", "2", "3", "4"],
}),
{
wrapper: TestWrapper({}),
},
);
await waitFor(() => {
expect(!result.current.queryResult.isFetching).toBeTruthy();
});
await waitFor(() =>
expect(result.current.selectProps.options).toHaveLength(2),
);
await waitFor(() =>
expect(result.current.selectProps.options).toEqual([
{
label:
"Necessitatibus necessitatibus id et cupiditate provident est qui amet.",
value: "1",
},
{ label: "Recusandae consectetur aut atque est.", value: "2" },
]),
);
});
it("defaultValue is not an array", async () => {
const { result } = renderHook(
() =>
useSelect({
resource: "posts",
defaultValue: "1",
}),
{
wrapper: TestWrapper({}),
},
);
await waitFor(() => {
expect(!result.current.queryResult.isFetching).toBeTruthy();
});
await waitFor(() =>
expect(result.current.selectProps.options).toHaveLength(2),
);
await waitFor(() =>
expect(result.current.selectProps.options).toEqual([
{
label:
"Necessitatibus necessitatibus id et cupiditate provident est qui amet.",
value: "1",
},
{ label: "Recusandae consectetur aut atque est.", value: "2" },
]),
);
});
it("should success data with resource with optionLabel and optionValue", async () => {
const { result } = renderHook(
() =>
useSelect<{ id: string; slug: string }>({
resource: "posts",
optionLabel: "slug",
optionValue: "id",
}),
{
wrapper: TestWrapper({}),
},
);
await waitFor(() => {
expect(!result.current.queryResult.isFetching).toBeTruthy();
});
await waitFor(() =>
expect(result.current.selectProps.options).toHaveLength(2),
);
await waitFor(() =>
expect(result.current.selectProps.options).toEqual([
{ label: "ut-ad-et", value: "1" },
{ label: "consequatur-molestiae-rerum", value: "2" },
]),
);
});
it("should generate options with custom optionLabel and optionValue functions", async () => {
const { result } = renderHook(
() =>
useSelect({
resource: "posts",
optionLabel: (item) => `${item.title} - ${item.userId}`,
optionValue: (item) => `${item.id}`,
}),
{
wrapper: TestWrapper({
dataProvider: MockJSONServer,
resources: [{ name: "posts" }],
}),
},
);
await waitFor(() => {
expect(result.current.queryResult.isSuccess).toBeTruthy();
});
const { selectProps } = result.current;
expect(selectProps.options).toHaveLength(2);
expect(selectProps.options).toEqual([
{
label:
"Necessitatibus necessitatibus id et cupiditate provident est qui amet. - 5",
value: "1",
},
{ label: "Recusandae consectetur aut atque est. - 36", value: "2" },
]);
});
it("should success data with resource with filters", async () => {
const { result } = renderHook(
() =>
useSelect<{ id: string; slug: string }>({
resource: "posts",
filters: [
{
field: "slug",
operator: "ncontains",
value: "test",
},
],
}),
{
wrapper: TestWrapper({}),
},
);
await waitFor(() => {
expect(result.current.queryResult.isSuccess).toBeTruthy();
});
await waitFor(() =>
expect(result.current.selectProps.options).toHaveLength(2),
);
await waitFor(() =>
expect(result.current.selectProps.options).toEqual([
{
label:
"Necessitatibus necessitatibus id et cupiditate provident est qui amet.",
value: "1",
},
{ label: "Recusandae consectetur aut atque est.", value: "2" },
]),
);
});
it("onSearch debounce with default value (300ms)", async () => {
const getListMock = jest.fn(() => Promise.resolve({ data: [], total: 0 }));
const { result } = renderHook(
() =>
useSelect({
resource: "posts",
debounce: 300,
}),
{
wrapper: TestWrapper({
dataProvider: {
...MockJSONServer,
getList: getListMock,
},
}),
},
);
await waitFor(() => {
expect(result.current.queryResult.isSuccess).toBeTruthy();
});
await waitFor(() => expect(getListMock).toBeCalledTimes(1));
const { selectProps } = result.current;
selectProps?.onSearch?.("1");
selectProps?.onSearch?.("12");
selectProps?.onSearch?.("123");
await waitFor(() => {
expect(!result.current.queryResult.isFetching).toBeTruthy();
});
await waitFor(() => expect(getListMock).toBeCalledTimes(2));
});
it("onSearch disabled debounce (0ms)", async () => {
const getListMock = jest.fn(() => {
return Promise.resolve({ data: [], total: 0 });
});
const { result } = renderHook(
() =>
useSelect({
resource: "posts",
debounce: 0,
}),
{
wrapper: TestWrapper({
dataProvider: {
...MockJSONServer,
getList: getListMock,
},
}),
},
);
await waitFor(() => {
expect(!result.current.queryResult.isFetching).toBeTruthy();
});
await waitFor(() => expect(getListMock).toBeCalledTimes(1));
const { selectProps } = result.current;
selectProps?.onSearch?.("1");
await waitFor(() => expect(getListMock).toBeCalledTimes(2));
selectProps?.onSearch?.("2");
await waitFor(() => expect(getListMock).toBeCalledTimes(3));
selectProps?.onSearch?.("3");
await waitFor(() => expect(getListMock).toBeCalledTimes(4));
});
it("should invoke queryOptions methods successfully", async () => {
const mockFunc = jest.fn();
const { result } = renderHook(
() =>
useSelect({
resource: "posts",
queryOptions: {
onSuccess: (data) => {
mockFunc();
},
},
}),
{
wrapper: TestWrapper({}),
},
);
await waitFor(() => {
expect(result.current.queryResult.isSuccess).toBeTruthy();
});
await waitFor(() =>
expect(result.current.selectProps.options).toHaveLength(2),
);
await waitFor(() =>
expect(result.current.selectProps.options).toEqual([
{
label:
"Necessitatibus necessitatibus id et cupiditate provident est qui amet.",
value: "1",
},
{ label: "Recusandae consectetur aut atque est.", value: "2" },
]),
);
expect(mockFunc).toBeCalled();
});
it("should invoke queryOptions methods for default value successfully", async () => {
const mockFunc = jest.fn();
const { result } = renderHook(
() =>
useSelect({
resource: "posts",
defaultValue: ["1", "2", "3", "4"],
defaultValueQueryOptions: {
onSuccess: (data) => {
mockFunc();
},
},
}),
{
wrapper: TestWrapper({}),
},
);
await waitFor(() => {
expect(result.current.queryResult.isSuccess).toBeTruthy();
});
await waitFor(() =>
expect(result.current.selectProps.options).toHaveLength(2),
);
await waitFor(() =>
expect(result.current.selectProps.options).toEqual([
{
label:
"Necessitatibus necessitatibus id et cupiditate provident est qui amet.",
value: "1",
},
{ label: "Recusandae consectetur aut atque est.", value: "2" },
]),
);
expect(mockFunc).toBeCalled();
});
it("should work with queryResult and query", async () => {
const { result } = renderHook(
() =>
useSelect({
resource: "posts",
defaultValue: ["1", "2", "3", "4"],
}),
{
wrapper: TestWrapper({
dataProvider: MockJSONServer,
resources: [{ name: "posts" }],
}),
},
);
await waitFor(() => {
expect(result.current.queryResult.isSuccess).toBeTruthy();
});
expect(result.current.query).toEqual(result.current.queryResult);
});
});

View File

@@ -0,0 +1,70 @@
import type { SelectProps } from "antd/lib/select";
import type { QueryObserverResult } from "@tanstack/react-query";
import {
useSelect as useSelectCore,
type BaseRecord,
type GetManyResponse,
type GetListResponse,
type HttpError,
type UseSelectProps,
type BaseOption,
} from "@refinedev/core";
export type UseSelectReturnType<
TData extends BaseRecord = BaseRecord,
TOption extends BaseOption = BaseOption,
> = {
selectProps: SelectProps<TOption>;
query: QueryObserverResult<GetListResponse<TData>>;
defaultValueQuery: QueryObserverResult<GetManyResponse<TData>>;
/**
* @deprecated Use `query` instead
*/
queryResult: QueryObserverResult<GetListResponse<TData>>;
/**
* @deprecated Use `defaultValueQuery` instead
*/
defaultValueQueryResult: QueryObserverResult<GetManyResponse<TData>>;
};
/**
* `useSelect` hook allows you to manage an Ant Design {@link https://ant.design/components/select/ Select} component when records in a resource needs to be used as select options.
*
* @see {@link https://refine.dev/docs/api-reference/antd/hooks/field/useSelect/} for more details.
*
* @typeParam TQueryFnData - Result data returned by the query function. Extends {@link https://refine.dev/docs/api-reference/core/interfaceReferences#baserecord `BaseRecord`}
* @typeParam TError - Custom error object that extends {@link https://refine.dev/docs/api-reference/core/interfaceReferences#httperror `HttpError`}
* @typeParam TData - Result data returned by the `select` function. Extends {@link https://refine.dev/docs/api-reference/core/interfaceReferences#baserecord `BaseRecord`}. Defaults to `TQueryFnData`
*
*/
export const useSelect = <
TQueryFnData extends BaseRecord = BaseRecord,
TError extends HttpError = HttpError,
TData extends BaseRecord = TQueryFnData,
TOption extends BaseOption = BaseOption,
>(
props: UseSelectProps<TQueryFnData, TError, TData>,
): UseSelectReturnType<TData, TOption> => {
const { query, defaultValueQuery, onSearch, options } = useSelectCore<
TQueryFnData,
TError,
TData,
TOption
>(props);
return {
selectProps: {
options,
onSearch,
loading: defaultValueQuery.isFetching,
showSearch: true,
filterOption: false,
},
query,
defaultValueQuery,
queryResult: query,
defaultValueQueryResult: defaultValueQuery,
};
};