mirror of
https://github.com/stefanpejcic/openpanel
synced 2025-06-26 18:28:26 +00:00
packages
This commit is contained in:
11
packages/core/src/hooks/auth/index.ts
Normal file
11
packages/core/src/hooks/auth/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export { usePermissions } from "./usePermissions";
|
||||
export { useGetIdentity } from "./useGetIdentity";
|
||||
export { useLogout } from "./useLogout";
|
||||
export { useLogin } from "./useLogin";
|
||||
export { useRegister } from "./useRegister";
|
||||
export { useForgotPassword } from "./useForgotPassword";
|
||||
export { useUpdatePassword } from "./useUpdatePassword";
|
||||
export { useAuthenticated, useIsAuthenticated } from "./useIsAuthenticated";
|
||||
export { useCheckError, useOnError } from "./useOnError";
|
||||
export { useIsExistAuthentication } from "./useIsExistAuthentication";
|
||||
export { useInvalidateAuthStore } from "./useInvalidateAuthStore";
|
||||
615
packages/core/src/hooks/auth/useForgotPassword/index.spec.ts
Normal file
615
packages/core/src/hooks/auth/useForgotPassword/index.spec.ts
Normal file
@@ -0,0 +1,615 @@
|
||||
import { renderHook, waitFor } from "@testing-library/react";
|
||||
|
||||
import {
|
||||
TestWrapper,
|
||||
act,
|
||||
mockLegacyRouterProvider,
|
||||
mockRouterProvider,
|
||||
queryClient,
|
||||
} from "@test";
|
||||
|
||||
import { useForgotPassword } from ".";
|
||||
|
||||
// NOTE : Will be removed in v5
|
||||
describe("v3LegacyAuthProviderCompatible useForgotPassword Hook", () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(console, "error").mockImplementation((message) => {
|
||||
if (message?.message === "Missing email") return;
|
||||
if (typeof message === "undefined") return;
|
||||
console.warn(message);
|
||||
});
|
||||
});
|
||||
|
||||
it("succeed forgot password", async () => {
|
||||
const { result } = renderHook(
|
||||
() => useForgotPassword({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
forgotPassword: ({ email }) => {
|
||||
if (email) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(new Error("Missing email"));
|
||||
},
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
getPermissions: () => Promise.resolve(),
|
||||
logout: () => Promise.resolve(),
|
||||
getUserIdentity: () => Promise.resolve({ id: 1 }),
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const { mutate: forgotPassword } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
forgotPassword({ email: "test@test.com" });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("succeed and redirect forgot password", async () => {
|
||||
const replaceMock = jest.fn();
|
||||
const legacyRouterProvider = {
|
||||
...mockLegacyRouterProvider(),
|
||||
useHistory: () => ({
|
||||
goBack: jest.fn(),
|
||||
replace: replaceMock,
|
||||
push: jest.fn(),
|
||||
}),
|
||||
};
|
||||
const { result } = renderHook(
|
||||
() => useForgotPassword({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyRouterProvider,
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
forgotPassword: ({ email }) => {
|
||||
if (email) {
|
||||
return Promise.resolve("/redirect-path");
|
||||
}
|
||||
return Promise.reject(new Error("Missing email"));
|
||||
},
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
getPermissions: () => Promise.resolve(),
|
||||
logout: () => Promise.resolve(),
|
||||
getUserIdentity: () => Promise.resolve({ id: 1 }),
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const { mutate: forgotPassword } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
forgotPassword({ email: "test@test.com" });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
expect(result.current.data).toBe("/redirect-path");
|
||||
expect(replaceMock).toBeCalledWith("/redirect-path");
|
||||
});
|
||||
});
|
||||
|
||||
it("succeed and redirect forgot password with routerProvider", async () => {
|
||||
const mockFn = jest.fn();
|
||||
const { result } = renderHook(
|
||||
() => useForgotPassword({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
routerProvider: mockRouterProvider({
|
||||
fns: {
|
||||
go: () => mockFn,
|
||||
},
|
||||
}),
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
forgotPassword: ({ email }) => {
|
||||
if (email) {
|
||||
return Promise.resolve("/redirect-path");
|
||||
}
|
||||
return Promise.reject(new Error("Missing email"));
|
||||
},
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
getPermissions: () => Promise.resolve(),
|
||||
logout: () => Promise.resolve(),
|
||||
getUserIdentity: () => Promise.resolve({ id: 1 }),
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const { mutate: forgotPassword } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
forgotPassword({ email: "test@test.com" });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
expect(result.current.data).toBe("/redirect-path");
|
||||
expect(mockFn).toBeCalledWith({
|
||||
to: "/redirect-path",
|
||||
type: "replace",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("fail forgot password", async () => {
|
||||
const { result } = renderHook(
|
||||
() => useForgotPassword({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
forgotPassword: () => Promise.reject(new Error("Missing email")),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
getPermissions: () => Promise.resolve(),
|
||||
logout: () => Promise.resolve(),
|
||||
getUserIdentity: () => Promise.resolve({ id: 1 }),
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const { mutate: forgotPassword } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
forgotPassword({});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isError).toBeTruthy();
|
||||
});
|
||||
|
||||
const { error } = result.current ?? { error: undefined };
|
||||
|
||||
expect(error).toEqual(new Error("Missing email"));
|
||||
});
|
||||
});
|
||||
|
||||
describe("useForgotPassword Hook", () => {
|
||||
const mockAuthProvider = {
|
||||
login: () =>
|
||||
Promise.resolve({
|
||||
success: true,
|
||||
}),
|
||||
check: () => Promise.resolve({ authenticated: true }),
|
||||
onError: () => Promise.resolve({}),
|
||||
logout: () => Promise.resolve({ success: true }),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(console, "error").mockImplementation((message) => {
|
||||
if (message?.message === "Missing email") return;
|
||||
if (typeof message === "undefined") return;
|
||||
console.warn(message);
|
||||
});
|
||||
});
|
||||
|
||||
it("succeed forgot password", async () => {
|
||||
const { result } = renderHook(() => useForgotPassword(), {
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
forgotPassword: (params: any) => {
|
||||
if (!params?.["email"]) {
|
||||
return Promise.resolve({ success: false });
|
||||
}
|
||||
return Promise.resolve({ success: true });
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: forgotPassword } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
forgotPassword({ email: "test@test.com" });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.data?.success).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("succeed and redirect forgot password", async () => {
|
||||
const { result } = renderHook(() => useForgotPassword(), {
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
forgotPassword: (params: any) => {
|
||||
if (!params?.["email"]) {
|
||||
return Promise.resolve({ success: false });
|
||||
}
|
||||
return Promise.resolve({
|
||||
success: true,
|
||||
redirectTo: "/rediect-path",
|
||||
});
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: forgotPassword } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
forgotPassword({ email: "test@test.com" });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.data?.success).toBeTruthy();
|
||||
expect(result.current.data?.redirectTo).toBe("/rediect-path");
|
||||
});
|
||||
});
|
||||
|
||||
it("succeed and redirect forgot password with legacyRouterProvider", async () => {
|
||||
const mockFn = jest.fn();
|
||||
const legacyRouterProvider = {
|
||||
...mockLegacyRouterProvider(),
|
||||
useHistory: () => ({
|
||||
replace: mockFn,
|
||||
}),
|
||||
};
|
||||
const { result } = renderHook(() => useForgotPassword(), {
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
forgotPassword: (params: any) => {
|
||||
if (!params?.["email"]) {
|
||||
return Promise.resolve({ success: false });
|
||||
}
|
||||
return Promise.resolve({
|
||||
success: true,
|
||||
redirectTo: "/rediect-path",
|
||||
});
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: forgotPassword } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
forgotPassword({ email: "test@test.com" });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.data?.success).toBeTruthy();
|
||||
expect(result.current.data?.redirectTo).toBe("/rediect-path");
|
||||
});
|
||||
});
|
||||
|
||||
it("fail forgot password", async () => {
|
||||
const { result } = renderHook(() => useForgotPassword(), {
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
forgotPassword: () =>
|
||||
Promise.resolve({
|
||||
success: false,
|
||||
error: new Error("Missing email"),
|
||||
}),
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: forgotPassword } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
forgotPassword({});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.data).toEqual({
|
||||
success: false,
|
||||
error: new Error("Missing email"),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should open notification when has error is true", async () => {
|
||||
const openNotificationMock = jest.fn();
|
||||
|
||||
const { result } = renderHook(() => useForgotPassword(), {
|
||||
wrapper: TestWrapper({
|
||||
notificationProvider: {
|
||||
open: openNotificationMock,
|
||||
close: jest.fn(),
|
||||
},
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
forgotPassword: () =>
|
||||
Promise.resolve({
|
||||
success: false,
|
||||
error: new Error("Missing email"),
|
||||
}),
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: forgotPassword } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
forgotPassword({});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(openNotificationMock).toBeCalledWith({
|
||||
key: "forgot-password-error",
|
||||
type: "error",
|
||||
message: "Error",
|
||||
description: "Missing email",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should open notification when has success is false, error is undefined", async () => {
|
||||
const openNotificationMock = jest.fn();
|
||||
|
||||
const { result } = renderHook(() => useForgotPassword(), {
|
||||
wrapper: TestWrapper({
|
||||
notificationProvider: {
|
||||
open: openNotificationMock,
|
||||
close: jest.fn(),
|
||||
},
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
forgotPassword: () =>
|
||||
Promise.resolve({
|
||||
success: false,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: forgotPassword } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
forgotPassword({});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(openNotificationMock).toBeCalledWith({
|
||||
key: "forgot-password-error",
|
||||
type: "error",
|
||||
message: "Forgot Password Error",
|
||||
description: "Error while resetting password",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should open notification when throw error", async () => {
|
||||
const openNotificationMock = jest.fn();
|
||||
|
||||
const { result } = renderHook(() => useForgotPassword(), {
|
||||
wrapper: TestWrapper({
|
||||
notificationProvider: {
|
||||
open: openNotificationMock,
|
||||
close: jest.fn(),
|
||||
},
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
forgotPassword: () => {
|
||||
throw new Error("Unhandled error");
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: forgotPassword } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
forgotPassword({});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(openNotificationMock).toBeCalledWith({
|
||||
key: "forgot-password-error",
|
||||
type: "error",
|
||||
message: "Error",
|
||||
description: "Unhandled error",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should override `mutationFn` with mutationOptions.mutationFn", async () => {
|
||||
const forgotPasswordMock = jest.fn().mockResolvedValue({ data: {} });
|
||||
const mutationFnMock = jest.fn().mockResolvedValue({ data: {} });
|
||||
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useForgotPassword({
|
||||
mutationOptions: {
|
||||
// mutationFn is omitted in types. So we need to use @ts-ignore test it.
|
||||
// @ts-ignore
|
||||
mutationFn: mutationFnMock,
|
||||
},
|
||||
}),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
forgotPassword: forgotPasswordMock,
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
result.current.mutate({});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(forgotPasswordMock).not.toBeCalled();
|
||||
expect(mutationFnMock).toBeCalled();
|
||||
});
|
||||
|
||||
it("should override `mutationKey` with `mutationOptions.mutationKey`", async () => {
|
||||
const forgotPasswordMock = jest.fn().mockResolvedValue({ data: {} });
|
||||
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useForgotPassword({
|
||||
mutationOptions: {
|
||||
mutationKey: ["foo", "bar"],
|
||||
},
|
||||
}),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
forgotPassword: forgotPasswordMock,
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
result.current.mutate({});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(
|
||||
queryClient.getMutationCache().findAll({
|
||||
mutationKey: ["foo", "bar"],
|
||||
}),
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("should open success notification when successNotification is passed", async () => {
|
||||
const openNotificationMock = jest.fn();
|
||||
|
||||
const { result } = renderHook(() => useForgotPassword(), {
|
||||
wrapper: TestWrapper({
|
||||
notificationProvider: {
|
||||
open: openNotificationMock,
|
||||
close: jest.fn(),
|
||||
},
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
forgotPassword: () =>
|
||||
Promise.resolve({
|
||||
success: true,
|
||||
successNotification: {
|
||||
message: "Password reset successful",
|
||||
description: "Your password has been successfully reset",
|
||||
},
|
||||
}),
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: forgotPassword } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
forgotPassword({ email: "test@test.com" });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(openNotificationMock).toBeCalledWith({
|
||||
key: "forgot-password-success",
|
||||
type: "success",
|
||||
message: "Password reset successful",
|
||||
description: "Your password has been successfully reset",
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// NOTE : Will be removed in v5
|
||||
describe("useForgotPassword Hook authProvider selection", () => {
|
||||
it("selects new authProvider", async () => {
|
||||
const legacyForgotPasswordMock = jest.fn(() => Promise.resolve());
|
||||
const forgotPasswordMock = jest.fn(() =>
|
||||
Promise.resolve({
|
||||
success: true,
|
||||
}),
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useForgotPassword(), {
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
forgotPassword: () => legacyForgotPasswordMock(),
|
||||
},
|
||||
authProvider: {
|
||||
login: () => Promise.resolve({ success: true }),
|
||||
check: () => Promise.resolve({ authenticated: true }),
|
||||
onError: () => Promise.resolve({}),
|
||||
logout: () => Promise.resolve({ success: true }),
|
||||
forgotPassword: () => forgotPasswordMock(),
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: forgotPassword } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
forgotPassword({});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBeFalsy();
|
||||
});
|
||||
|
||||
expect(legacyForgotPasswordMock).not.toHaveBeenCalled();
|
||||
expect(forgotPasswordMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("selects v3LegacyAuthProviderCompatible authProvider", async () => {
|
||||
const legacyForgotPasswordMock = jest.fn(() => Promise.resolve());
|
||||
const forgotPasswordMock = jest.fn(() =>
|
||||
Promise.resolve({ success: true }),
|
||||
);
|
||||
|
||||
const { result } = renderHook(
|
||||
() => useForgotPassword({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
forgotPassword: () => legacyForgotPasswordMock(),
|
||||
},
|
||||
authProvider: {
|
||||
login: () => Promise.resolve({ success: true }),
|
||||
check: () => Promise.resolve({ authenticated: true }),
|
||||
onError: () => Promise.resolve({}),
|
||||
logout: () => Promise.resolve({ success: true }),
|
||||
forgotPassword: () => forgotPasswordMock(),
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const { mutate: forgotPassword } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
forgotPassword({});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBeFalsy();
|
||||
});
|
||||
|
||||
expect(legacyForgotPasswordMock).toHaveBeenCalled();
|
||||
expect(forgotPasswordMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
222
packages/core/src/hooks/auth/useForgotPassword/index.ts
Normal file
222
packages/core/src/hooks/auth/useForgotPassword/index.ts
Normal file
@@ -0,0 +1,222 @@
|
||||
import { getXRay } from "@refinedev/devtools-internal";
|
||||
import {
|
||||
type UseMutationOptions,
|
||||
type UseMutationResult,
|
||||
useMutation,
|
||||
} from "@tanstack/react-query";
|
||||
|
||||
import { useAuthBindingsContext, useLegacyAuthContext } from "@contexts/auth";
|
||||
import {
|
||||
useGo,
|
||||
useKeys,
|
||||
useNavigation,
|
||||
useNotification,
|
||||
useRouterType,
|
||||
} from "@hooks";
|
||||
|
||||
import type {
|
||||
AuthActionResponse,
|
||||
SuccessNotificationResponse,
|
||||
TForgotPasswordData,
|
||||
} from "../../../contexts/auth/types";
|
||||
import type { RefineError } from "../../../contexts/data/types";
|
||||
import type { OpenNotificationParams } from "../../../contexts/notification/types";
|
||||
|
||||
export type UseForgotPasswordLegacyProps<TVariables> = {
|
||||
v3LegacyAuthProviderCompatible: true;
|
||||
mutationOptions?: Omit<
|
||||
UseMutationOptions<
|
||||
TForgotPasswordData,
|
||||
Error | RefineError,
|
||||
TVariables,
|
||||
unknown
|
||||
>,
|
||||
"mutationFn" | "onError" | "onSuccess"
|
||||
>;
|
||||
};
|
||||
|
||||
export type UseForgotPasswordProps<TVariables> = {
|
||||
v3LegacyAuthProviderCompatible?: false;
|
||||
mutationOptions?: Omit<
|
||||
UseMutationOptions<
|
||||
AuthActionResponse,
|
||||
Error | RefineError,
|
||||
TVariables,
|
||||
unknown
|
||||
>,
|
||||
"mutationFn"
|
||||
>;
|
||||
};
|
||||
|
||||
export type UseForgotPasswordCombinedProps<TVariables> = {
|
||||
v3LegacyAuthProviderCompatible: boolean;
|
||||
mutationOptions?: Omit<
|
||||
UseMutationOptions<
|
||||
AuthActionResponse | TForgotPasswordData,
|
||||
Error | RefineError,
|
||||
TVariables,
|
||||
unknown
|
||||
>,
|
||||
"mutationFn"
|
||||
>;
|
||||
};
|
||||
|
||||
export type UseForgotPasswordLegacyReturnType<TVariables> = UseMutationResult<
|
||||
TForgotPasswordData,
|
||||
Error | RefineError,
|
||||
TVariables,
|
||||
unknown
|
||||
>;
|
||||
|
||||
export type UseForgotPasswordReturnType<TVariables> = UseMutationResult<
|
||||
AuthActionResponse,
|
||||
Error | RefineError,
|
||||
TVariables,
|
||||
unknown
|
||||
>;
|
||||
|
||||
export type UseForgotPasswordCombinedReturnType<TVariables> = UseMutationResult<
|
||||
AuthActionResponse | TForgotPasswordData,
|
||||
Error | RefineError,
|
||||
TVariables,
|
||||
unknown
|
||||
>;
|
||||
|
||||
export function useForgotPassword<TVariables = {}>(
|
||||
props: UseForgotPasswordLegacyProps<TVariables>,
|
||||
): UseForgotPasswordLegacyReturnType<TVariables>;
|
||||
|
||||
export function useForgotPassword<TVariables = {}>(
|
||||
props?: UseForgotPasswordProps<TVariables>,
|
||||
): UseForgotPasswordReturnType<TVariables>;
|
||||
|
||||
export function useForgotPassword<TVariables = {}>(
|
||||
props?: UseForgotPasswordCombinedProps<TVariables>,
|
||||
): UseForgotPasswordCombinedReturnType<TVariables>;
|
||||
|
||||
/**
|
||||
* `useForgotPassword` calls `forgotPassword` method from {@link https://refine.dev/docs/api-reference/core/providers/auth-provider `authProvider`} under the hood.
|
||||
*
|
||||
* @see {@link https://refine.dev/docs/api-reference/core/hooks/auth/useForgotPassword} for more details.
|
||||
*
|
||||
* @typeParam TData - Result data of the query
|
||||
* @typeParam TVariables - Values for mutation function. default `{}`
|
||||
*
|
||||
*/
|
||||
export function useForgotPassword<TVariables = {}>({
|
||||
v3LegacyAuthProviderCompatible,
|
||||
mutationOptions,
|
||||
}:
|
||||
| UseForgotPasswordProps<TVariables>
|
||||
| UseForgotPasswordLegacyProps<TVariables> = {}):
|
||||
| UseForgotPasswordReturnType<TVariables>
|
||||
| UseForgotPasswordLegacyReturnType<TVariables> {
|
||||
const routerType = useRouterType();
|
||||
const go = useGo();
|
||||
const { replace } = useNavigation();
|
||||
const {
|
||||
forgotPassword: v3LegacyAuthProviderCompatibleForgotPasswordFromContext,
|
||||
} = useLegacyAuthContext();
|
||||
const { forgotPassword: forgotPasswordFromContext } =
|
||||
useAuthBindingsContext();
|
||||
const { close, open } = useNotification();
|
||||
const { keys, preferLegacyKeys } = useKeys();
|
||||
|
||||
const mutation = useMutation<
|
||||
AuthActionResponse,
|
||||
Error | RefineError,
|
||||
TVariables,
|
||||
unknown
|
||||
>({
|
||||
mutationKey: keys().auth().action("forgotPassword").get(preferLegacyKeys),
|
||||
mutationFn: forgotPasswordFromContext,
|
||||
onSuccess: ({ success, redirectTo, error, successNotification }) => {
|
||||
if (success) {
|
||||
close?.("forgot-password-error");
|
||||
|
||||
if (successNotification) {
|
||||
open?.(buildSuccessNotification(successNotification));
|
||||
}
|
||||
}
|
||||
|
||||
if (error || !success) {
|
||||
open?.(buildNotification(error));
|
||||
}
|
||||
|
||||
if (redirectTo) {
|
||||
if (routerType === "legacy") {
|
||||
replace(redirectTo);
|
||||
} else {
|
||||
go({ to: redirectTo, type: "replace" });
|
||||
}
|
||||
}
|
||||
},
|
||||
onError: (error: any) => {
|
||||
open?.(buildNotification(error));
|
||||
},
|
||||
...(v3LegacyAuthProviderCompatible === true ? {} : mutationOptions),
|
||||
meta: {
|
||||
...(v3LegacyAuthProviderCompatible === true ? {} : mutationOptions?.meta),
|
||||
...getXRay("useForgotPassword", preferLegacyKeys),
|
||||
},
|
||||
});
|
||||
|
||||
const v3LegacyAuthProviderCompatibleMutation = useMutation<
|
||||
TForgotPasswordData,
|
||||
Error | RefineError,
|
||||
TVariables,
|
||||
unknown
|
||||
>({
|
||||
mutationKey: [
|
||||
...keys().auth().action("forgotPassword").get(preferLegacyKeys),
|
||||
"v3LegacyAuthProviderCompatible",
|
||||
],
|
||||
mutationFn: v3LegacyAuthProviderCompatibleForgotPasswordFromContext,
|
||||
onSuccess: (redirectPathFromAuth) => {
|
||||
if (redirectPathFromAuth !== false) {
|
||||
if (redirectPathFromAuth) {
|
||||
if (routerType === "legacy") {
|
||||
replace(redirectPathFromAuth);
|
||||
} else {
|
||||
go({ to: redirectPathFromAuth, type: "replace" });
|
||||
}
|
||||
}
|
||||
}
|
||||
close?.("forgot-password-error");
|
||||
},
|
||||
onError: (error: any) => {
|
||||
open?.(buildNotification(error));
|
||||
},
|
||||
...(v3LegacyAuthProviderCompatible ? mutationOptions : {}),
|
||||
meta: {
|
||||
...(v3LegacyAuthProviderCompatible ? mutationOptions?.meta : {}),
|
||||
...getXRay("useForgotPassword", preferLegacyKeys),
|
||||
},
|
||||
});
|
||||
|
||||
return v3LegacyAuthProviderCompatible
|
||||
? v3LegacyAuthProviderCompatibleMutation
|
||||
: mutation;
|
||||
}
|
||||
|
||||
const buildNotification = (
|
||||
error?: Error | RefineError,
|
||||
): OpenNotificationParams => {
|
||||
return {
|
||||
message: error?.name || "Forgot Password Error",
|
||||
description: error?.message || "Error while resetting password",
|
||||
key: "forgot-password-error",
|
||||
type: "error",
|
||||
};
|
||||
};
|
||||
|
||||
const buildSuccessNotification = (
|
||||
successNotification: SuccessNotificationResponse,
|
||||
): OpenNotificationParams => {
|
||||
return {
|
||||
message: successNotification.message,
|
||||
description: successNotification.description,
|
||||
key: "forgot-password-success",
|
||||
type: "success",
|
||||
};
|
||||
};
|
||||
286
packages/core/src/hooks/auth/useGetIdentity/index.spec.ts
Normal file
286
packages/core/src/hooks/auth/useGetIdentity/index.spec.ts
Normal file
@@ -0,0 +1,286 @@
|
||||
import { renderHook, waitFor } from "@testing-library/react";
|
||||
|
||||
import { TestWrapper, queryClient } from "@test";
|
||||
|
||||
import { useGetIdentity } from "./";
|
||||
|
||||
// NOTE : Will be removed in v5
|
||||
describe("v3LegacyAuthProviderCompatible useGetIdentity Hook", () => {
|
||||
it("returns object useGetIdentity", async () => {
|
||||
const { result } = renderHook(
|
||||
() => useGetIdentity({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
getPermissions: () => Promise.resolve(),
|
||||
logout: () => Promise.resolve(),
|
||||
getUserIdentity: () => Promise.resolve({ id: 1 }),
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(result.current?.data).toEqual({ id: 1 });
|
||||
});
|
||||
|
||||
it("throw error useGetIdentity", async () => {
|
||||
jest.spyOn(console, "error").mockImplementation((message) => {
|
||||
if (message?.message === "Not Authenticated") return;
|
||||
console.warn(message);
|
||||
});
|
||||
|
||||
const { result } = renderHook(
|
||||
() => useGetIdentity({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
getPermissions: () => Promise.resolve(),
|
||||
logout: () => Promise.resolve(),
|
||||
getUserIdentity: () =>
|
||||
Promise.reject(new Error("Not Authenticated")),
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isError).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(result.current?.error).toEqual(new Error("Not Authenticated"));
|
||||
});
|
||||
|
||||
it("throw error useGetIdentity undefined", async () => {
|
||||
const { result } = renderHook(
|
||||
() => useGetIdentity({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
getPermissions: () => Promise.resolve(),
|
||||
logout: () => Promise.resolve(),
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(!result.current.isFetching).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(result.current.status).toEqual("loading");
|
||||
expect(result.current.data).not.toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("useGetIdentity Hook", () => {
|
||||
const mockAuthProvider = {
|
||||
login: () => Promise.resolve({ success: true }),
|
||||
check: () => Promise.resolve({ authenticated: true }),
|
||||
onError: () => Promise.resolve({}),
|
||||
logout: () => Promise.resolve({ success: true }),
|
||||
};
|
||||
|
||||
it("returns object useGetIdentity", async () => {
|
||||
const { result } = renderHook(
|
||||
() => useGetIdentity({ v3LegacyAuthProviderCompatible: false }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
getIdentity: () => Promise.resolve({ id: 1 }),
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.data).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(result.current?.data).toEqual({ id: 1 });
|
||||
});
|
||||
|
||||
it("return error useGetIdentity", async () => {
|
||||
jest.spyOn(console, "error").mockImplementation((message) => {
|
||||
if (message?.message === "Not Authenticated") return;
|
||||
console.warn(message);
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useGetIdentity(), {
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
getIdentity: () => Promise.resolve(new Error("Not Authenticated")),
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.data).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(result.current?.data).toEqual(new Error("Not Authenticated"));
|
||||
});
|
||||
|
||||
it("throw error useGetIdentity undefined", async () => {
|
||||
const { result } = renderHook(() => useGetIdentity(), {
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(!result.current.isFetching).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(result.current.status).toEqual("loading");
|
||||
expect(result.current.data).not.toBeDefined();
|
||||
});
|
||||
|
||||
it("should override `queryKey` with `queryOptions.queryKey`", async () => {
|
||||
const getIdentityMock = jest.fn().mockResolvedValue({
|
||||
data: { id: 1, title: "foo" },
|
||||
});
|
||||
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useGetIdentity({
|
||||
queryOptions: {
|
||||
queryKey: ["foo", "bar"],
|
||||
},
|
||||
}),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
getIdentity: getIdentityMock,
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(
|
||||
queryClient.getQueryCache().findAll({
|
||||
queryKey: ["foo", "bar"],
|
||||
}),
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("should override `queryFn` with `queryOptions.queryFn`", async () => {
|
||||
const getIdentityMock = jest.fn().mockResolvedValue({
|
||||
data: [{ id: 1, title: "foo" }],
|
||||
});
|
||||
const queryFnMock = jest.fn().mockResolvedValue({
|
||||
data: [{ id: 1, title: "foo" }],
|
||||
});
|
||||
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useGetIdentity({
|
||||
queryOptions: {
|
||||
queryFn: queryFnMock,
|
||||
},
|
||||
}),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
getIdentity: getIdentityMock,
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(getIdentityMock).not.toBeCalled();
|
||||
expect(queryFnMock).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
// NOTE : Will be removed in v5
|
||||
describe("useGetIdentity Hook authProvider selection", () => {
|
||||
it("selects new authProvider", async () => {
|
||||
const legacyGetUserIdentityMock = jest.fn(() => Promise.resolve());
|
||||
const getIdentitiyMock = jest.fn(() => Promise.resolve());
|
||||
|
||||
const { result } = renderHook(() => useGetIdentity(), {
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
getUserIdentity: () => legacyGetUserIdentityMock(),
|
||||
},
|
||||
authProvider: {
|
||||
login: () => Promise.resolve({ success: true }),
|
||||
check: () => Promise.resolve({ authenticated: true }),
|
||||
onError: () => Promise.resolve({}),
|
||||
logout: () => Promise.resolve({ success: true }),
|
||||
getIdentity: () => getIdentitiyMock(),
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(!result.current.isLoading).toBeFalsy();
|
||||
});
|
||||
|
||||
expect(legacyGetUserIdentityMock).not.toHaveBeenCalled();
|
||||
expect(getIdentitiyMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("selects v3LegacyAuthProviderCompatible authProvider", async () => {
|
||||
const legacyGetUserIdentityMock = jest.fn(() => Promise.resolve());
|
||||
const getIdentitiyMock = jest.fn(() => Promise.resolve());
|
||||
|
||||
const { result } = renderHook(
|
||||
() => useGetIdentity({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
getUserIdentity: () => legacyGetUserIdentityMock(),
|
||||
},
|
||||
authProvider: {
|
||||
login: () => Promise.resolve({ success: true }),
|
||||
check: () => Promise.resolve({ authenticated: true }),
|
||||
onError: () => Promise.resolve({}),
|
||||
logout: () => Promise.resolve({ success: true }),
|
||||
getIdentity: () => getIdentitiyMock(),
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(!result.current.isLoading).toBeFalsy();
|
||||
});
|
||||
|
||||
expect(legacyGetUserIdentityMock).toHaveBeenCalled();
|
||||
expect(getIdentitiyMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
104
packages/core/src/hooks/auth/useGetIdentity/index.ts
Normal file
104
packages/core/src/hooks/auth/useGetIdentity/index.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { getXRay } from "@refinedev/devtools-internal";
|
||||
import {
|
||||
type UseQueryOptions,
|
||||
type UseQueryResult,
|
||||
useQuery,
|
||||
} from "@tanstack/react-query";
|
||||
|
||||
import { useAuthBindingsContext, useLegacyAuthContext } from "@contexts/auth";
|
||||
import { useKeys } from "@hooks/useKeys";
|
||||
|
||||
import type { IdentityResponse } from "../../../contexts/auth/types";
|
||||
|
||||
export type UseGetIdentityLegacyProps<TData> = {
|
||||
v3LegacyAuthProviderCompatible: true;
|
||||
queryOptions?: UseQueryOptions<TData>;
|
||||
};
|
||||
|
||||
export type UseGetIdentityProps<TData = IdentityResponse> = {
|
||||
v3LegacyAuthProviderCompatible?: false;
|
||||
queryOptions?: UseQueryOptions<TData>;
|
||||
};
|
||||
|
||||
export type UseGetIdentityCombinedProps<TData = any> = {
|
||||
v3LegacyAuthProviderCompatible: boolean;
|
||||
queryOptions?: UseQueryOptions<TData> | UseQueryOptions<IdentityResponse>;
|
||||
};
|
||||
|
||||
export type UseGetIdentityLegacyReturnType<TData> = UseQueryResult<
|
||||
TData,
|
||||
unknown
|
||||
>;
|
||||
|
||||
export type UseGetIdentityReturnType<TData = IdentityResponse> = UseQueryResult<
|
||||
TData,
|
||||
unknown
|
||||
>;
|
||||
|
||||
export type UsePermissionsCombinedReturnType<TData = any> =
|
||||
| UseQueryResult<TData, unknown>
|
||||
| UseQueryResult<IdentityResponse, unknown>;
|
||||
|
||||
export function useGetIdentity<TData = any>(
|
||||
props: UseGetIdentityLegacyProps<TData>,
|
||||
): UseGetIdentityLegacyReturnType<TData>;
|
||||
|
||||
export function useGetIdentity<TData = IdentityResponse>(
|
||||
props?: UseGetIdentityProps<TData>,
|
||||
): UseGetIdentityReturnType<TData>;
|
||||
|
||||
export function useGetIdentity<TData = any>(
|
||||
props?: UseGetIdentityCombinedProps<TData>,
|
||||
): UsePermissionsCombinedReturnType<TData>;
|
||||
|
||||
/**
|
||||
* `useGetIdentity` calls the `getUserIdentity` method from the {@link https://refine.dev/docs/core/providers/auth-provider `authProvider`} under the hood.
|
||||
*
|
||||
* @see {@link https://refine.dev/docs/api-reference/core/hooks/auth/useGetIdentity} for more details.
|
||||
*
|
||||
* @typeParam TData - Result data of the query
|
||||
*
|
||||
*/
|
||||
export function useGetIdentity<TData = any>({
|
||||
v3LegacyAuthProviderCompatible = false,
|
||||
queryOptions,
|
||||
}: UseGetIdentityProps<TData> | UseGetIdentityLegacyProps<TData> = {}):
|
||||
| UseGetIdentityReturnType<TData>
|
||||
| UseGetIdentityLegacyReturnType<TData> {
|
||||
const { getUserIdentity: legacyGetUserIdentity } = useLegacyAuthContext();
|
||||
const { getIdentity } = useAuthBindingsContext();
|
||||
const { keys, preferLegacyKeys } = useKeys();
|
||||
|
||||
const queryResponse = useQuery<TData>({
|
||||
queryKey: keys().auth().action("identity").get(preferLegacyKeys),
|
||||
// Enabled check for `getIdentity` is enough to be sure that it's defined in the query function but TS is not smart enough to know that.
|
||||
queryFn:
|
||||
(getIdentity as (params?: unknown) => Promise<TData>) ??
|
||||
(() => Promise.resolve({})),
|
||||
enabled: !v3LegacyAuthProviderCompatible && !!getIdentity,
|
||||
retry: false,
|
||||
...(v3LegacyAuthProviderCompatible === true ? {} : queryOptions),
|
||||
meta: {
|
||||
...(v3LegacyAuthProviderCompatible === true ? {} : queryOptions?.meta),
|
||||
...getXRay("useGetIdentity", preferLegacyKeys),
|
||||
},
|
||||
});
|
||||
|
||||
const legacyQueryResponse = useQuery<TData>({
|
||||
queryKey: [
|
||||
...keys().auth().action("identity").get(preferLegacyKeys),
|
||||
"v3LegacyAuthProviderCompatible",
|
||||
],
|
||||
// Enabled check for `getUserIdentity` is enough to be sure that it's defined in the query function but TS is not smart enough to know that.
|
||||
queryFn: legacyGetUserIdentity ?? (() => Promise.resolve({})),
|
||||
enabled: v3LegacyAuthProviderCompatible && !!legacyGetUserIdentity,
|
||||
retry: false,
|
||||
...(v3LegacyAuthProviderCompatible ? queryOptions : {}),
|
||||
meta: {
|
||||
...(v3LegacyAuthProviderCompatible ? queryOptions?.meta : {}),
|
||||
...getXRay("useGetIdentity", preferLegacyKeys),
|
||||
},
|
||||
});
|
||||
|
||||
return v3LegacyAuthProviderCompatible ? legacyQueryResponse : queryResponse;
|
||||
}
|
||||
19
packages/core/src/hooks/auth/useInvalidateAuthStore/index.ts
Normal file
19
packages/core/src/hooks/auth/useInvalidateAuthStore/index.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { useKeys } from "@hooks/useKeys";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
export const useInvalidateAuthStore = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { keys, preferLegacyKeys } = useKeys();
|
||||
|
||||
const invalidate = async () => {
|
||||
await Promise.all(
|
||||
(["check", "identity", "permissions"] as const).map((action) =>
|
||||
queryClient.invalidateQueries(
|
||||
keys().auth().action(action).get(preferLegacyKeys),
|
||||
),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
return invalidate;
|
||||
};
|
||||
307
packages/core/src/hooks/auth/useIsAuthenticated/index.spec.ts
Normal file
307
packages/core/src/hooks/auth/useIsAuthenticated/index.spec.ts
Normal file
@@ -0,0 +1,307 @@
|
||||
import { renderHook, waitFor } from "@testing-library/react";
|
||||
|
||||
import { TestWrapper, mockLegacyRouterProvider } from "@test";
|
||||
|
||||
import { useIsAuthenticated } from ".";
|
||||
import * as authContext from "../../../contexts/auth";
|
||||
|
||||
const mockFn = jest.fn();
|
||||
|
||||
const legacyRouterProvider = {
|
||||
...mockLegacyRouterProvider(),
|
||||
useHistory: () => ({
|
||||
goBack: jest.fn(),
|
||||
replace: mockFn,
|
||||
push: jest.fn(),
|
||||
}),
|
||||
};
|
||||
|
||||
describe("useIsAuthenticated Hook", () => {
|
||||
it("returns authenticated true", async () => {
|
||||
const { result } = renderHook(() => useIsAuthenticated(), {
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
login: () => Promise.resolve({ success: false }),
|
||||
check: () =>
|
||||
Promise.resolve({
|
||||
authenticated: true,
|
||||
}),
|
||||
onError: () => Promise.resolve({}),
|
||||
logout: () => Promise.resolve({ success: false }),
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.data?.authenticated).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("returns authenticated false and called checkError", async () => {
|
||||
jest.spyOn(console, "error").mockImplementation((message) => {
|
||||
if (message?.message === "Not Authenticated") return;
|
||||
console.warn(message);
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useIsAuthenticated(), {
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
login: () => Promise.resolve({ success: false }),
|
||||
check: () =>
|
||||
Promise.resolve({
|
||||
authenticated: false,
|
||||
error: new Error("Not Authenticated"),
|
||||
}),
|
||||
onError: () => Promise.resolve({}),
|
||||
logout: () => Promise.resolve({ success: false }),
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.data?.error).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("returns authenticated false and called checkError with custom redirect path", async () => {
|
||||
jest.spyOn(console, "error").mockImplementation((message) => {
|
||||
if (message?.redirectPath === "/custom-url") return;
|
||||
console.warn(message);
|
||||
});
|
||||
|
||||
const checkErrorMock = jest.fn();
|
||||
const { result } = renderHook(() => useIsAuthenticated(), {
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
login: () => Promise.resolve({ success: false }),
|
||||
check: () =>
|
||||
Promise.resolve({
|
||||
authenticated: false,
|
||||
redirectTo: "/custom-url",
|
||||
error: new Error("Not Authenticated"),
|
||||
}),
|
||||
onError: checkErrorMock,
|
||||
logout: () => Promise.resolve({ success: false }),
|
||||
},
|
||||
legacyRouterProvider,
|
||||
}),
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.data?.error).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("should resolve {} if no authProvider is provided", async () => {
|
||||
jest.spyOn(authContext, "useAuthBindingsContext").mockReturnValue({
|
||||
...jest.requireActual("../../../contexts/auth"),
|
||||
check: undefined,
|
||||
});
|
||||
|
||||
const { result } = renderHook(
|
||||
() => useIsAuthenticated({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
login: () => Promise.resolve({ success: false }),
|
||||
check: () => Promise.resolve({ authenticated: true }),
|
||||
onError: () => Promise.resolve({}),
|
||||
logout: () => Promise.resolve({ success: false }),
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(result.current.data).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
// NOTE : Will be removed in v5
|
||||
describe("v3LegacyAuthProviderCompatible useIsAuthenticated Hook", () => {
|
||||
it("returns authenticated true", async () => {
|
||||
const { result } = renderHook(
|
||||
() => useIsAuthenticated({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
getPermissions: () => Promise.resolve(),
|
||||
logout: () => Promise.resolve(),
|
||||
getUserIdentity: () => Promise.resolve({ id: 1 }),
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("returns authenticated false and called checkError", async () => {
|
||||
jest.spyOn(console, "error").mockImplementation((message) => {
|
||||
if (message?.message === "Not Authenticated") return;
|
||||
console.warn(message);
|
||||
});
|
||||
|
||||
const checkErrorMock = jest.fn();
|
||||
const { result } = renderHook(
|
||||
() => useIsAuthenticated({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.reject(new Error("Not Authenticated")),
|
||||
checkError: checkErrorMock,
|
||||
getPermissions: () => Promise.resolve(),
|
||||
logout: () => Promise.resolve(),
|
||||
getUserIdentity: () => Promise.resolve({ id: 1 }),
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isError).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("returns authenticated false and called checkError with custom redirect path", async () => {
|
||||
jest.spyOn(console, "error").mockImplementation((message) => {
|
||||
if (message?.redirectPath === "/custom-url") return;
|
||||
console.warn(message);
|
||||
});
|
||||
|
||||
const checkErrorMock = jest.fn();
|
||||
const { result } = renderHook(
|
||||
() => useIsAuthenticated({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.reject({ redirectPath: "/custom-url" }),
|
||||
checkError: checkErrorMock,
|
||||
getPermissions: () => Promise.resolve(),
|
||||
logout: () => Promise.resolve(),
|
||||
getUserIdentity: () => Promise.resolve({ id: 1 }),
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isError).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("should resolve {} if no authProvider is provided", async () => {
|
||||
jest.spyOn(authContext, "useLegacyAuthContext").mockReturnValue({
|
||||
...jest.requireActual("../../../contexts/auth"),
|
||||
checkAuth: undefined,
|
||||
});
|
||||
|
||||
const { result } = renderHook(
|
||||
() => useIsAuthenticated({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: undefined,
|
||||
checkError: () => Promise.resolve(),
|
||||
getPermissions: () => Promise.resolve(),
|
||||
logout: () => Promise.resolve(),
|
||||
getUserIdentity: () => Promise.resolve({ id: 1 }),
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(result.current.data).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
// NOTE : Will be removed in v5
|
||||
describe("useIsAuthenticated Hook authProvider selection", () => {
|
||||
beforeEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("selects new authProvider", async () => {
|
||||
const legacyCheckAuthMock = jest.fn(() => Promise.resolve());
|
||||
const checkMock = jest.fn(() =>
|
||||
Promise.resolve({
|
||||
authenticated: true,
|
||||
}),
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useIsAuthenticated(), {
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => legacyCheckAuthMock(),
|
||||
checkError: () => Promise.resolve(),
|
||||
logout: () => Promise.resolve(),
|
||||
},
|
||||
authProvider: {
|
||||
login: () => Promise.resolve({ success: true }),
|
||||
check: () => checkMock(),
|
||||
onError: () => Promise.resolve({}),
|
||||
logout: () => Promise.resolve({ success: true }),
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBeFalsy();
|
||||
});
|
||||
|
||||
expect(legacyCheckAuthMock).not.toHaveBeenCalled();
|
||||
expect(checkMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("selects v3LegacyAuthProviderCompatible authProvider", async () => {
|
||||
const legacyCheckAuthMock = jest.fn(() => Promise.resolve());
|
||||
const checkMock = jest.fn(() => Promise.resolve({ authenticated: true }));
|
||||
|
||||
const { result } = renderHook(
|
||||
() => useIsAuthenticated({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => legacyCheckAuthMock(),
|
||||
checkError: () => Promise.resolve(),
|
||||
logout: () => Promise.resolve(),
|
||||
},
|
||||
authProvider: {
|
||||
login: () => Promise.resolve({ success: true }),
|
||||
check: () => checkMock(),
|
||||
onError: () => Promise.resolve({}),
|
||||
logout: () => Promise.resolve({ success: true }),
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBeFalsy();
|
||||
});
|
||||
|
||||
expect(legacyCheckAuthMock).toHaveBeenCalled();
|
||||
expect(checkMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
93
packages/core/src/hooks/auth/useIsAuthenticated/index.ts
Normal file
93
packages/core/src/hooks/auth/useIsAuthenticated/index.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { getXRay } from "@refinedev/devtools-internal";
|
||||
import { type UseQueryResult, useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { useAuthBindingsContext, useLegacyAuthContext } from "@contexts/auth";
|
||||
import { useKeys } from "@hooks/useKeys";
|
||||
|
||||
import type { CheckResponse } from "../../../contexts/auth/types";
|
||||
|
||||
export type UseIsAuthenticatedLegacyProps = {
|
||||
v3LegacyAuthProviderCompatible: true;
|
||||
params?: any;
|
||||
};
|
||||
|
||||
export type UseIsAuthenticatedProps = {
|
||||
v3LegacyAuthProviderCompatible?: false;
|
||||
params?: any;
|
||||
};
|
||||
|
||||
export type UseIsAuthenticatedCombinedProps = {
|
||||
v3LegacyAuthProviderCompatible: boolean;
|
||||
params?: any;
|
||||
};
|
||||
|
||||
export type UseIsAuthenticatedLegacyReturnType = UseQueryResult<any, any>;
|
||||
|
||||
export type UseIsAuthenticatedReturnType = UseQueryResult<CheckResponse, any>;
|
||||
|
||||
export type UseIsAuthenticatedCombinedReturnType = UseQueryResult<
|
||||
CheckResponse | any,
|
||||
any
|
||||
>;
|
||||
|
||||
export function useIsAuthenticated(
|
||||
props: UseIsAuthenticatedLegacyProps,
|
||||
): UseIsAuthenticatedLegacyReturnType;
|
||||
|
||||
export function useIsAuthenticated(
|
||||
props?: UseIsAuthenticatedProps,
|
||||
): UseIsAuthenticatedReturnType;
|
||||
|
||||
export function useIsAuthenticated(
|
||||
props?: UseIsAuthenticatedCombinedProps,
|
||||
): UseIsAuthenticatedCombinedReturnType;
|
||||
|
||||
/**
|
||||
* `useIsAuthenticated` calls the `checkAuth` method from the {@link https://refine.dev/docs/core/providers/auth-provider `authProvider`} under the hood.
|
||||
*
|
||||
* @see {@link https://refine.dev/docs/api-reference/core/hooks/auth/useAuthenticated} for more details.
|
||||
*/
|
||||
export function useIsAuthenticated({
|
||||
v3LegacyAuthProviderCompatible = false,
|
||||
params,
|
||||
}: UseIsAuthenticatedProps | UseIsAuthenticatedLegacyProps = {}):
|
||||
| UseIsAuthenticatedReturnType
|
||||
| UseIsAuthenticatedLegacyReturnType {
|
||||
const { checkAuth } = useLegacyAuthContext();
|
||||
const { check } = useAuthBindingsContext();
|
||||
const { keys, preferLegacyKeys } = useKeys();
|
||||
|
||||
const queryResponse = useQuery({
|
||||
queryKey: keys()
|
||||
.auth()
|
||||
.action("check")
|
||||
.params(params)
|
||||
.get(preferLegacyKeys),
|
||||
queryFn: async () => (await check?.(params)) ?? {},
|
||||
retry: false,
|
||||
enabled: !v3LegacyAuthProviderCompatible,
|
||||
meta: {
|
||||
...getXRay("useIsAuthenticated", preferLegacyKeys),
|
||||
},
|
||||
});
|
||||
|
||||
const legacyQueryResponse = useQuery({
|
||||
queryKey: [
|
||||
...keys().auth().action("check").params(params).get(preferLegacyKeys),
|
||||
"v3LegacyAuthProviderCompatible",
|
||||
],
|
||||
queryFn: async () => (await checkAuth?.(params)) ?? {},
|
||||
retry: false,
|
||||
enabled: v3LegacyAuthProviderCompatible,
|
||||
meta: {
|
||||
...getXRay("useIsAuthenticated", preferLegacyKeys),
|
||||
},
|
||||
});
|
||||
|
||||
return v3LegacyAuthProviderCompatible ? legacyQueryResponse : queryResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated `useAuthenticated` is deprecated with refine@4, use `useIsAuthenticated` instead, however, we still support `useAuthenticated` for backward compatibility.
|
||||
*/
|
||||
export const useAuthenticated = useIsAuthenticated;
|
||||
@@ -0,0 +1,43 @@
|
||||
import { TestWrapper } from "@test/index";
|
||||
import { renderHook } from "@testing-library/react";
|
||||
import { useIsExistAuthentication } from ".";
|
||||
|
||||
describe("useActiveAuthProvider", () => {
|
||||
it("authProvider", async () => {
|
||||
const { result } = renderHook(() => useIsExistAuthentication(), {
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
login: () => Promise.resolve({ success: true }),
|
||||
check: () => Promise.resolve({ authenticated: true }),
|
||||
onError: () => Promise.resolve({}),
|
||||
logout: () => Promise.resolve({ success: true }),
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
expect(result.current).toBeTruthy();
|
||||
});
|
||||
|
||||
it("v3LegacyAuthProviderCompatible authProvider", async () => {
|
||||
const { result } = renderHook(() => useIsExistAuthentication(), {
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
logout: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
expect(result.current).toBeTruthy();
|
||||
});
|
||||
|
||||
it("returns false", async () => {
|
||||
const { result } = renderHook(() => useIsExistAuthentication(), {
|
||||
wrapper: TestWrapper({}),
|
||||
});
|
||||
|
||||
expect(result.current).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import { useAuthBindingsContext, useLegacyAuthContext } from "@contexts/auth";
|
||||
|
||||
/**
|
||||
* A hook that the UI uses
|
||||
* @internal
|
||||
*/
|
||||
export const useIsExistAuthentication = (): boolean => {
|
||||
const { isProvided: legacyIsProvided } = useLegacyAuthContext();
|
||||
const { isProvided } = useAuthBindingsContext();
|
||||
|
||||
return Boolean(isProvided || legacyIsProvided);
|
||||
};
|
||||
1034
packages/core/src/hooks/auth/useLogin/index.spec.ts
Normal file
1034
packages/core/src/hooks/auth/useLogin/index.spec.ts
Normal file
File diff suppressed because it is too large
Load Diff
261
packages/core/src/hooks/auth/useLogin/index.ts
Normal file
261
packages/core/src/hooks/auth/useLogin/index.ts
Normal file
@@ -0,0 +1,261 @@
|
||||
import React from "react";
|
||||
|
||||
import { getXRay } from "@refinedev/devtools-internal";
|
||||
import {
|
||||
type UseMutationOptions,
|
||||
type UseMutationResult,
|
||||
useMutation,
|
||||
} from "@tanstack/react-query";
|
||||
import qs from "qs";
|
||||
|
||||
import { useAuthBindingsContext, useLegacyAuthContext } from "@contexts/auth";
|
||||
import {
|
||||
useGo,
|
||||
useKeys,
|
||||
useNavigation,
|
||||
useNotification,
|
||||
useParsed,
|
||||
useRouterContext,
|
||||
useRouterType,
|
||||
} from "@hooks";
|
||||
|
||||
import type {
|
||||
AuthActionResponse,
|
||||
SuccessNotificationResponse,
|
||||
TLoginData,
|
||||
} from "../../../contexts/auth/types";
|
||||
import type { RefineError } from "../../../contexts/data/types";
|
||||
import type { OpenNotificationParams } from "../../../contexts/notification/types";
|
||||
import { useInvalidateAuthStore } from "../useInvalidateAuthStore";
|
||||
|
||||
export type UseLoginLegacyProps<TVariables> = {
|
||||
v3LegacyAuthProviderCompatible: true;
|
||||
mutationOptions?: Omit<
|
||||
UseMutationOptions<TLoginData, Error | RefineError, TVariables, unknown>,
|
||||
"mutationFn" | "onError" | "onSuccess"
|
||||
>;
|
||||
};
|
||||
|
||||
export type UseLoginProps<TVariables> = {
|
||||
v3LegacyAuthProviderCompatible?: false;
|
||||
mutationOptions?: Omit<
|
||||
UseMutationOptions<
|
||||
AuthActionResponse,
|
||||
Error | RefineError,
|
||||
TVariables,
|
||||
unknown
|
||||
>,
|
||||
"mutationFn"
|
||||
>;
|
||||
};
|
||||
|
||||
export type UseLoginCombinedProps<TVariables> = {
|
||||
v3LegacyAuthProviderCompatible: boolean;
|
||||
mutationOptions?: Omit<
|
||||
UseMutationOptions<
|
||||
AuthActionResponse | TLoginData,
|
||||
Error | RefineError,
|
||||
TVariables,
|
||||
unknown
|
||||
>,
|
||||
"mutationFn"
|
||||
>;
|
||||
};
|
||||
|
||||
export type UseLoginLegacyReturnType<TVariables> = UseMutationResult<
|
||||
TLoginData,
|
||||
Error | RefineError,
|
||||
TVariables,
|
||||
unknown
|
||||
>;
|
||||
|
||||
export type UseLoginReturnType<TVariables> = UseMutationResult<
|
||||
AuthActionResponse,
|
||||
Error | RefineError,
|
||||
TVariables,
|
||||
unknown
|
||||
>;
|
||||
|
||||
export type UseLoginCombinedReturnType<TVariables> = UseMutationResult<
|
||||
AuthActionResponse | TLoginData,
|
||||
Error | RefineError,
|
||||
TVariables,
|
||||
unknown
|
||||
>;
|
||||
|
||||
export function useLogin<TVariables = {}>(
|
||||
props: UseLoginLegacyProps<TVariables>,
|
||||
): UseLoginLegacyReturnType<TVariables>;
|
||||
|
||||
export function useLogin<TVariables = {}>(
|
||||
props?: UseLoginProps<TVariables>,
|
||||
): UseLoginReturnType<TVariables>;
|
||||
|
||||
export function useLogin<TVariables = {}>(
|
||||
props?: UseLoginCombinedProps<TVariables>,
|
||||
): UseLoginCombinedReturnType<TVariables>;
|
||||
|
||||
/**
|
||||
* `useLogin` calls `login` method from {@link https://refine.dev/docs/api-reference/core/providers/auth-provider `authProvider`} under the hood.
|
||||
*
|
||||
* @see {@link https://refine.dev/docs/api-reference/core/hooks/auth/useLogin} for more details.
|
||||
*
|
||||
* @typeParam TData - Result data of the query
|
||||
* @typeParam TVariables - Values for mutation function. default `{}`
|
||||
*
|
||||
*/
|
||||
export function useLogin<TVariables = {}>({
|
||||
v3LegacyAuthProviderCompatible,
|
||||
mutationOptions,
|
||||
}: UseLoginProps<TVariables> | UseLoginLegacyProps<TVariables> = {}):
|
||||
| UseLoginLegacyReturnType<TVariables>
|
||||
| UseLoginReturnType<TVariables> {
|
||||
const invalidateAuthStore = useInvalidateAuthStore();
|
||||
const routerType = useRouterType();
|
||||
|
||||
const go = useGo();
|
||||
const { replace } = useNavigation();
|
||||
|
||||
const parsed = useParsed();
|
||||
|
||||
const { useLocation } = useRouterContext();
|
||||
const { search } = useLocation();
|
||||
|
||||
const { close, open } = useNotification();
|
||||
const { login: legacyLoginFromContext } = useLegacyAuthContext();
|
||||
const { login: loginFromContext } = useAuthBindingsContext();
|
||||
const { keys, preferLegacyKeys } = useKeys();
|
||||
|
||||
const to = React.useMemo(() => {
|
||||
if (routerType === "legacy") {
|
||||
const legacySearch = qs.parse(search, {
|
||||
ignoreQueryPrefix: true,
|
||||
});
|
||||
return legacySearch.to;
|
||||
}
|
||||
return parsed.params?.to;
|
||||
}, [routerType, parsed.params, search]);
|
||||
|
||||
const mutation = useMutation<
|
||||
AuthActionResponse,
|
||||
Error | RefineError,
|
||||
TVariables,
|
||||
unknown
|
||||
>({
|
||||
mutationKey: keys().auth().action("login").get(preferLegacyKeys),
|
||||
mutationFn: loginFromContext,
|
||||
onSuccess: async ({ success, redirectTo, error, successNotification }) => {
|
||||
if (success) {
|
||||
close?.("login-error");
|
||||
|
||||
if (successNotification) {
|
||||
open?.(buildSuccessNotification(successNotification));
|
||||
}
|
||||
}
|
||||
|
||||
if (error || !success) {
|
||||
open?.(buildNotification(error));
|
||||
}
|
||||
|
||||
if (to && success) {
|
||||
if (routerType === "legacy") {
|
||||
replace(to as string);
|
||||
} else {
|
||||
go({ to: to as string, type: "replace" });
|
||||
}
|
||||
} else if (redirectTo) {
|
||||
if (routerType === "legacy") {
|
||||
replace(redirectTo);
|
||||
} else {
|
||||
go({ to: redirectTo, type: "replace" });
|
||||
}
|
||||
} else {
|
||||
if (routerType === "legacy") {
|
||||
replace("/");
|
||||
}
|
||||
}
|
||||
|
||||
await invalidateAuthStore();
|
||||
},
|
||||
onError: (error: any) => {
|
||||
open?.(buildNotification(error));
|
||||
},
|
||||
...(v3LegacyAuthProviderCompatible === true ? {} : mutationOptions),
|
||||
meta: {
|
||||
...(v3LegacyAuthProviderCompatible === true ? {} : mutationOptions?.meta),
|
||||
...getXRay("useLogin", preferLegacyKeys),
|
||||
},
|
||||
});
|
||||
|
||||
const v3LegacyAuthProviderCompatibleMutation = useMutation<
|
||||
TLoginData,
|
||||
Error | RefineError,
|
||||
TVariables,
|
||||
unknown
|
||||
>({
|
||||
mutationKey: [
|
||||
...keys().auth().action("login").get(preferLegacyKeys),
|
||||
"v3LegacyAuthProviderCompatible",
|
||||
],
|
||||
mutationFn: legacyLoginFromContext,
|
||||
onSuccess: async (redirectPathFromAuth) => {
|
||||
if (to) {
|
||||
replace(to as string);
|
||||
}
|
||||
|
||||
if (redirectPathFromAuth !== false && !to) {
|
||||
if (typeof redirectPathFromAuth === "string") {
|
||||
if (routerType === "legacy") {
|
||||
replace(redirectPathFromAuth);
|
||||
} else {
|
||||
go({ to: redirectPathFromAuth, type: "replace" });
|
||||
}
|
||||
} else {
|
||||
if (routerType === "legacy") {
|
||||
replace("/");
|
||||
} else {
|
||||
go({ to: "/", type: "replace" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await invalidateAuthStore();
|
||||
|
||||
close?.("login-error");
|
||||
},
|
||||
onError: (error: any) => {
|
||||
open?.(buildNotification(error));
|
||||
},
|
||||
...(v3LegacyAuthProviderCompatible ? mutationOptions : {}),
|
||||
meta: {
|
||||
...(v3LegacyAuthProviderCompatible ? mutationOptions?.meta : {}),
|
||||
...getXRay("useLogin", preferLegacyKeys),
|
||||
},
|
||||
});
|
||||
|
||||
return v3LegacyAuthProviderCompatible
|
||||
? v3LegacyAuthProviderCompatibleMutation
|
||||
: mutation;
|
||||
}
|
||||
|
||||
const buildNotification = (
|
||||
error?: Error | RefineError,
|
||||
): OpenNotificationParams => {
|
||||
return {
|
||||
message: error?.name || "Login Error",
|
||||
description: error?.message || "Invalid credentials",
|
||||
key: "login-error",
|
||||
type: "error",
|
||||
};
|
||||
};
|
||||
|
||||
const buildSuccessNotification = (
|
||||
successNotification: SuccessNotificationResponse,
|
||||
): OpenNotificationParams => {
|
||||
return {
|
||||
message: successNotification.message,
|
||||
description: successNotification.description,
|
||||
key: "login-success",
|
||||
type: "success",
|
||||
};
|
||||
};
|
||||
804
packages/core/src/hooks/auth/useLogout/index.spec.ts
Normal file
804
packages/core/src/hooks/auth/useLogout/index.spec.ts
Normal file
@@ -0,0 +1,804 @@
|
||||
import { renderHook, waitFor } from "@testing-library/react";
|
||||
|
||||
import { TestWrapper, act, mockRouterProvider, queryClient } from "@test";
|
||||
|
||||
import { useCheckError, useOnError } from "../useOnError";
|
||||
import { useLogout } from "./";
|
||||
|
||||
const mockGo = jest.fn();
|
||||
|
||||
const routerProvider = mockRouterProvider({
|
||||
fns: {
|
||||
go: () => mockGo,
|
||||
},
|
||||
});
|
||||
|
||||
// NOTE : Will be removed in v5
|
||||
describe("v3LegacyAuthProviderCompatible useLogout Hook", () => {
|
||||
beforeEach(() => {
|
||||
mockGo.mockReset();
|
||||
|
||||
jest.spyOn(console, "error").mockImplementation((message) => {
|
||||
if (
|
||||
message?.message === "Logout rejected" ||
|
||||
typeof message === "undefined"
|
||||
)
|
||||
return;
|
||||
|
||||
console.warn(message);
|
||||
});
|
||||
});
|
||||
|
||||
it("logout and redirect to login", async () => {
|
||||
const { result } = renderHook(
|
||||
() => useLogout({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
isProvided: true,
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
getPermissions: () => Promise.resolve(),
|
||||
logout: () => {
|
||||
return Promise.resolve();
|
||||
},
|
||||
getUserIdentity: () => Promise.resolve(),
|
||||
},
|
||||
routerProvider,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const { mutateAsync: logout } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
await logout();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
return !result.current?.isLoading;
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
expect(mockGo).toBeCalledWith({ to: "/login" });
|
||||
});
|
||||
});
|
||||
|
||||
it("logout and not redirect", async () => {
|
||||
const { result } = renderHook(
|
||||
() => useLogout({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
isProvided: true,
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
getPermissions: () => Promise.resolve(),
|
||||
logout: () => {
|
||||
return Promise.resolve(false);
|
||||
},
|
||||
getUserIdentity: () => Promise.resolve(),
|
||||
},
|
||||
routerProvider,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const { mutateAsync: logout } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
await logout();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
return !result.current?.isLoading;
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
expect(mockGo).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("logout and redirect to custom path", async () => {
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useLogout<{ redirectPath: string }>({
|
||||
v3LegacyAuthProviderCompatible: true,
|
||||
}),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
isProvided: true,
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
getPermissions: () => Promise.resolve(),
|
||||
logout: ({ redirectPath }) => {
|
||||
return Promise.resolve(redirectPath);
|
||||
},
|
||||
getUserIdentity: () => Promise.resolve(),
|
||||
},
|
||||
routerProvider,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const { mutateAsync: logout } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
await logout({ redirectPath: "/custom-path" });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
return result.current?.status === "success";
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
expect(mockGo).toBeCalledWith({ to: "/custom-path" });
|
||||
});
|
||||
});
|
||||
|
||||
it("logout rejected", async () => {
|
||||
const { result } = renderHook(
|
||||
() => useLogout({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
isProvided: true,
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
getPermissions: () => Promise.resolve(),
|
||||
logout: () => Promise.reject(new Error("Logout rejected")),
|
||||
getUserIdentity: () => Promise.resolve(),
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const { mutateAsync: logout } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
try {
|
||||
await logout();
|
||||
} catch (error) {
|
||||
expect(error).toEqual(new Error("Logout rejected"));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("logout rejected with undefined error", async () => {
|
||||
const { result } = renderHook(
|
||||
() => useLogout({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
isProvided: true,
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
getPermissions: () => Promise.resolve(),
|
||||
logout: () => Promise.reject(),
|
||||
getUserIdentity: () => Promise.resolve(),
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const { mutateAsync: logout } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
try {
|
||||
await logout();
|
||||
} catch (error) {
|
||||
expect(error).not.toBeDefined();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("logout and not redirect if check error rejected with false", async () => {
|
||||
const { result } = renderHook(() => useCheckError(), {
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
isProvided: true,
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.reject(false),
|
||||
getPermissions: () => Promise.resolve(),
|
||||
logout: () => Promise.resolve(),
|
||||
getUserIdentity: () => Promise.resolve(),
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: checkError } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
await checkError({});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(!result.current.isLoading).toBeTruthy();
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
expect(mockGo).toBeCalledTimes(0);
|
||||
});
|
||||
});
|
||||
|
||||
it("logout and not redirect if logout resolved false", async () => {
|
||||
const { result } = renderHook(() => useCheckError(), {
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
isProvided: true,
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.reject(),
|
||||
getPermissions: () => Promise.resolve(),
|
||||
logout: () => Promise.resolve(false),
|
||||
getUserIdentity: () => Promise.resolve(),
|
||||
},
|
||||
routerProvider,
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: checkError } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
await checkError({});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(!result.current.isLoading).toBeTruthy();
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
expect(mockGo).toBeCalledTimes(0);
|
||||
});
|
||||
});
|
||||
|
||||
it("logout and redirect to resolved custom path", async () => {
|
||||
const { result } = renderHook(
|
||||
() => useLogout({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
isProvided: true,
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.reject(),
|
||||
getPermissions: () => Promise.resolve(),
|
||||
logout: () => Promise.resolve("/custom-path"),
|
||||
getUserIdentity: () => Promise.resolve(),
|
||||
},
|
||||
routerProvider,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const { mutate: logout } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
await logout();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
return result.current?.status === "success";
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
expect(mockGo).toBeCalledWith({ to: "/custom-path" });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("useLogout Hook", () => {
|
||||
const mockAuthProvider = {
|
||||
login: () => Promise.resolve({ success: false }),
|
||||
check: () => Promise.resolve({ authenticated: false }),
|
||||
onError: () => Promise.resolve({}),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockGo.mockReset();
|
||||
jest.spyOn(console, "error").mockImplementation((message) => {
|
||||
if (
|
||||
message?.message === "Logout rejected" ||
|
||||
typeof message === "undefined"
|
||||
)
|
||||
return;
|
||||
|
||||
console.warn(message);
|
||||
});
|
||||
});
|
||||
|
||||
it("logout and redirect to login", async () => {
|
||||
const { result } = renderHook(() => useLogout(), {
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
logout: () => {
|
||||
return Promise.resolve({
|
||||
success: true,
|
||||
redirectTo: "/login",
|
||||
});
|
||||
},
|
||||
},
|
||||
routerProvider,
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutateAsync: logout } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
await logout();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
return !result.current?.isLoading;
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
expect(mockGo).toBeCalledWith({ to: "/login" });
|
||||
});
|
||||
});
|
||||
|
||||
it("logout and not redirect", async () => {
|
||||
const { result } = renderHook(() => useLogout(), {
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
logout: () => {
|
||||
return Promise.resolve({
|
||||
success: true,
|
||||
});
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutateAsync: logout } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
await logout();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
return !result.current?.isLoading;
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
expect(mockGo).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("logout and redirect to custom path, pass redirect with hook's param", async () => {
|
||||
const { result } = renderHook(() => useLogout<{ redirectPath: string }>(), {
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
logout: () => {
|
||||
return Promise.resolve({
|
||||
success: true,
|
||||
});
|
||||
},
|
||||
},
|
||||
routerProvider,
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutateAsync: logout } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
await logout({ redirectPath: "/custom-path" });
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
expect(mockGo).toBeCalledWith(
|
||||
expect.objectContaining({ to: "/custom-path" }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("logout and redirect to custom path, pass redirect with authProvider return value", async () => {
|
||||
const { result } = renderHook(() => useLogout<{ redirectPath: string }>(), {
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
logout: () => {
|
||||
return Promise.resolve({
|
||||
success: true,
|
||||
redirectTo: "/custom-path",
|
||||
});
|
||||
},
|
||||
},
|
||||
routerProvider,
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutateAsync: logout } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
await logout();
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
expect(mockGo).toBeCalledWith({ to: "/custom-path" });
|
||||
});
|
||||
});
|
||||
|
||||
it("logout rejected", async () => {
|
||||
const { result } = renderHook(() => useLogout(), {
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
login: () => Promise.resolve({ success: true }),
|
||||
check: () => Promise.resolve({ authenticated: true }),
|
||||
onError: () => Promise.resolve({}),
|
||||
logout: () => {
|
||||
return Promise.resolve({
|
||||
success: false,
|
||||
error: new Error("Logout rejected"),
|
||||
});
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutateAsync: logout } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
await logout();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.data).toEqual({
|
||||
success: false,
|
||||
error: new Error("Logout rejected"),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("logout rejected with undefined error", async () => {
|
||||
const { result } = renderHook(() => useLogout(), {
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
logout: () => {
|
||||
return Promise.resolve({
|
||||
success: false,
|
||||
});
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutateAsync: logout } = result.current;
|
||||
await act(async () => {
|
||||
await logout();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.data).toEqual({
|
||||
success: false,
|
||||
});
|
||||
});
|
||||
|
||||
expect(result.current.data?.error).toBeUndefined();
|
||||
});
|
||||
|
||||
it("logout and not redirect if success false", async () => {
|
||||
const { result } = renderHook(() => useOnError(), {
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
logout: () => {
|
||||
return Promise.resolve({
|
||||
success: false,
|
||||
});
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: onError } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
await onError({});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(!result.current.isLoading).toBeTruthy();
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
expect(mockGo).toBeCalledTimes(0);
|
||||
});
|
||||
});
|
||||
|
||||
it("should open notification when has error is true", async () => {
|
||||
const openNotificationMock = jest.fn();
|
||||
|
||||
const { result } = renderHook(() => useLogout(), {
|
||||
wrapper: TestWrapper({
|
||||
notificationProvider: {
|
||||
open: openNotificationMock,
|
||||
close: jest.fn(),
|
||||
},
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
logout: () =>
|
||||
Promise.resolve({
|
||||
success: false,
|
||||
error: new Error("Error"),
|
||||
}),
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: forgotPassword } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
forgotPassword({});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(openNotificationMock).toBeCalledWith({
|
||||
key: "useLogout-error",
|
||||
type: "error",
|
||||
message: "Error",
|
||||
description: "Error",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should open notification when has success is false, error is undefined", async () => {
|
||||
const openNotificationMock = jest.fn();
|
||||
|
||||
const { result } = renderHook(() => useLogout(), {
|
||||
wrapper: TestWrapper({
|
||||
notificationProvider: {
|
||||
open: openNotificationMock,
|
||||
close: jest.fn(),
|
||||
},
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
logout: () => Promise.resolve({ success: false }),
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: forgotPassword } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
forgotPassword({});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(openNotificationMock).toBeCalledWith({
|
||||
key: "useLogout-error",
|
||||
type: "error",
|
||||
message: "Logout Error",
|
||||
description: "Something went wrong during logout",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should open notification when throw error", async () => {
|
||||
const openNotificationMock = jest.fn();
|
||||
|
||||
const { result } = renderHook(() => useLogout(), {
|
||||
wrapper: TestWrapper({
|
||||
notificationProvider: {
|
||||
open: openNotificationMock,
|
||||
close: jest.fn(),
|
||||
},
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
logout: () => {
|
||||
throw new Error("Unhandled error");
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: forgotPassword } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
forgotPassword({});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(openNotificationMock).toBeCalledWith({
|
||||
key: "useLogout-error",
|
||||
type: "error",
|
||||
message: "Error",
|
||||
description: "Unhandled error",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should override `mutationFn` with mutationOptions.mutationFn", async () => {
|
||||
const logoutMock = jest.fn().mockResolvedValue({ data: {} });
|
||||
const mutationFnMock = jest.fn().mockResolvedValue({ data: {} });
|
||||
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useLogout({
|
||||
mutationOptions: {
|
||||
// mutationFn is omitted in types. So we need to use @ts-ignore test it.
|
||||
// @ts-ignore
|
||||
mutationFn: mutationFnMock,
|
||||
},
|
||||
}),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
logout: logoutMock,
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
result.current.mutate({});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(logoutMock).not.toBeCalled();
|
||||
expect(mutationFnMock).toBeCalled();
|
||||
});
|
||||
|
||||
it("should override `mutationKey` with `mutationOptions.mutationKey`", async () => {
|
||||
const logoutMock = jest.fn().mockResolvedValue({ data: {} });
|
||||
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useLogout({
|
||||
mutationOptions: {
|
||||
mutationKey: ["foo", "bar"],
|
||||
},
|
||||
}),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
logout: logoutMock,
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
result.current.mutate({});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(
|
||||
queryClient.getMutationCache().findAll({
|
||||
mutationKey: ["foo", "bar"],
|
||||
}),
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("should open success notification when successNotification is passed", async () => {
|
||||
const openNotificationMock = jest.fn();
|
||||
|
||||
const successNotification = {
|
||||
message: "Logged out successfully!",
|
||||
description: "Operation completed successfully",
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useLogout(), {
|
||||
wrapper: TestWrapper({
|
||||
notificationProvider: {
|
||||
open: openNotificationMock,
|
||||
close: jest.fn(),
|
||||
},
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
logout: () =>
|
||||
Promise.resolve({
|
||||
success: true,
|
||||
successNotification,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync();
|
||||
});
|
||||
|
||||
expect(openNotificationMock).toHaveBeenCalledWith({
|
||||
key: "logout-success",
|
||||
type: "success",
|
||||
message: "Logged out successfully!",
|
||||
description: "Operation completed successfully",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// NOTE : Will be removed in v5
|
||||
describe("useLogout Hook authProvider selection", () => {
|
||||
it("selects new authProvider", async () => {
|
||||
const legacyLogoutMock = jest.fn(() => Promise.resolve());
|
||||
const logoutMock = jest.fn(() =>
|
||||
Promise.resolve({
|
||||
success: true,
|
||||
}),
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useLogout(), {
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
logout: () => legacyLogoutMock(),
|
||||
},
|
||||
authProvider: {
|
||||
login: () => Promise.resolve({ success: true }),
|
||||
check: () => Promise.resolve({ authenticated: true }),
|
||||
onError: () => Promise.resolve({}),
|
||||
logout: () => logoutMock(),
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: login } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
login({});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBeFalsy();
|
||||
});
|
||||
|
||||
expect(legacyLogoutMock).not.toHaveBeenCalled();
|
||||
expect(logoutMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("selects v3LegacyAuthProviderCompatible authProvider", async () => {
|
||||
const legacyLogoutMock = jest.fn(() => Promise.resolve());
|
||||
const logoutMock = jest.fn(() => Promise.resolve({ success: true }));
|
||||
|
||||
const { result } = renderHook(
|
||||
() => useLogout({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
logout: () => legacyLogoutMock(),
|
||||
},
|
||||
authProvider: {
|
||||
login: () => Promise.resolve({ success: true }),
|
||||
check: () => Promise.resolve({ authenticated: true }),
|
||||
onError: () => Promise.resolve({}),
|
||||
logout: () => logoutMock(),
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const { mutate: login } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
login({});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBeFalsy();
|
||||
});
|
||||
|
||||
expect(legacyLogoutMock).toHaveBeenCalled();
|
||||
expect(logoutMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
241
packages/core/src/hooks/auth/useLogout/index.ts
Normal file
241
packages/core/src/hooks/auth/useLogout/index.ts
Normal file
@@ -0,0 +1,241 @@
|
||||
import { getXRay } from "@refinedev/devtools-internal";
|
||||
import {
|
||||
type UseMutationOptions,
|
||||
type UseMutationResult,
|
||||
useMutation,
|
||||
} from "@tanstack/react-query";
|
||||
|
||||
import { useAuthBindingsContext, useLegacyAuthContext } from "@contexts/auth";
|
||||
import {
|
||||
useGo,
|
||||
useKeys,
|
||||
useNavigation,
|
||||
useNotification,
|
||||
useRouterType,
|
||||
} from "@hooks";
|
||||
|
||||
import type {
|
||||
AuthActionResponse,
|
||||
SuccessNotificationResponse,
|
||||
TLogoutData,
|
||||
} from "../../../contexts/auth/types";
|
||||
import type { RefineError } from "../../../contexts/data/types";
|
||||
import type { OpenNotificationParams } from "../../../contexts/notification/types";
|
||||
import { useInvalidateAuthStore } from "../useInvalidateAuthStore";
|
||||
|
||||
type Variables = {
|
||||
redirectPath?: string | false;
|
||||
};
|
||||
|
||||
export type UseLogoutLegacyProps<TVariables> = {
|
||||
v3LegacyAuthProviderCompatible: true;
|
||||
mutationOptions?: Omit<
|
||||
UseMutationOptions<
|
||||
TLogoutData,
|
||||
Error | RefineError,
|
||||
(TVariables & Variables) | void,
|
||||
unknown
|
||||
>,
|
||||
"mutationFn" | "onError" | "onSuccess"
|
||||
>;
|
||||
};
|
||||
|
||||
export type UseLogoutProps<TVariables> = {
|
||||
v3LegacyAuthProviderCompatible?: false;
|
||||
mutationOptions?: Omit<
|
||||
UseMutationOptions<
|
||||
AuthActionResponse,
|
||||
Error | RefineError,
|
||||
(TVariables & Variables) | void,
|
||||
unknown
|
||||
>,
|
||||
"mutationFn"
|
||||
>;
|
||||
};
|
||||
|
||||
export type UseLogoutCombinedProps<TVariables> = {
|
||||
v3LegacyAuthProviderCompatible: boolean;
|
||||
mutationOptions?: Omit<
|
||||
UseMutationOptions<
|
||||
AuthActionResponse | TLogoutData,
|
||||
Error | RefineError,
|
||||
(TVariables & Variables) | void,
|
||||
unknown
|
||||
>,
|
||||
"mutationFn"
|
||||
>;
|
||||
};
|
||||
|
||||
export type UseLogoutLegacyReturnType<TVariables> = UseMutationResult<
|
||||
TLogoutData,
|
||||
Error | RefineError,
|
||||
(TVariables & Variables) | void,
|
||||
unknown
|
||||
>;
|
||||
|
||||
export type UseLogoutReturnType<TVariables> = UseMutationResult<
|
||||
AuthActionResponse,
|
||||
Error | RefineError,
|
||||
(TVariables & Variables) | void,
|
||||
unknown
|
||||
>;
|
||||
|
||||
export type UseLogoutCombinedReturnType<TVariables> = UseMutationResult<
|
||||
AuthActionResponse | TLogoutData,
|
||||
Error | RefineError,
|
||||
(TVariables & Variables) | void,
|
||||
unknown
|
||||
>;
|
||||
|
||||
export function useLogout<TVariables = {}>(
|
||||
props: UseLogoutLegacyProps<TVariables>,
|
||||
): UseLogoutLegacyReturnType<TVariables>;
|
||||
|
||||
export function useLogout<TVariables = {}>(
|
||||
props?: UseLogoutProps<TVariables>,
|
||||
): UseLogoutReturnType<TVariables>;
|
||||
|
||||
export function useLogout<TVariables = {}>(
|
||||
props?: UseLogoutCombinedProps<TVariables>,
|
||||
): UseLogoutCombinedReturnType<TVariables>;
|
||||
|
||||
/**
|
||||
* `useLogout` calls the `logout` method from the {@link https://refine.dev/docs/api-reference/core/providers/auth-provider `authProvider`} under the hood.
|
||||
*
|
||||
* @see {@link https://refine.dev/docs/api-reference/core/hooks/auth/useLogout} for more details.
|
||||
*
|
||||
*/
|
||||
export function useLogout<TVariables = {}>({
|
||||
v3LegacyAuthProviderCompatible,
|
||||
mutationOptions,
|
||||
}: UseLogoutProps<TVariables> | UseLogoutLegacyProps<TVariables> = {}):
|
||||
| UseLogoutLegacyReturnType<TVariables>
|
||||
| UseLogoutReturnType<TVariables> {
|
||||
const invalidateAuthStore = useInvalidateAuthStore();
|
||||
const routerType = useRouterType();
|
||||
const go = useGo();
|
||||
const { push } = useNavigation();
|
||||
const { open, close } = useNotification();
|
||||
const { logout: legacyLogoutFromContext } = useLegacyAuthContext();
|
||||
const { logout: logoutFromContext } = useAuthBindingsContext();
|
||||
const { keys, preferLegacyKeys } = useKeys();
|
||||
|
||||
const mutation = useMutation<
|
||||
AuthActionResponse,
|
||||
Error | RefineError,
|
||||
(TVariables & Variables) | void,
|
||||
unknown
|
||||
>({
|
||||
mutationKey: keys().auth().action("logout").get(preferLegacyKeys),
|
||||
mutationFn: logoutFromContext,
|
||||
onSuccess: async (data, variables) => {
|
||||
const { success, error, redirectTo, successNotification } = data;
|
||||
const { redirectPath } = variables ?? {};
|
||||
|
||||
const redirect = redirectPath ?? redirectTo;
|
||||
|
||||
if (success) {
|
||||
close?.("useLogout-error");
|
||||
|
||||
if (successNotification) {
|
||||
open?.(buildSuccessNotification(successNotification));
|
||||
}
|
||||
}
|
||||
|
||||
if (error || !success) {
|
||||
open?.(buildNotification(error));
|
||||
}
|
||||
|
||||
if (redirect !== false) {
|
||||
if (routerType === "legacy") {
|
||||
push(redirect ?? "/login");
|
||||
} else {
|
||||
if (redirect) {
|
||||
go({ to: redirect });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await invalidateAuthStore();
|
||||
},
|
||||
onError: (error: any) => {
|
||||
open?.(buildNotification(error));
|
||||
},
|
||||
...(v3LegacyAuthProviderCompatible === true ? {} : mutationOptions),
|
||||
meta: {
|
||||
...(v3LegacyAuthProviderCompatible === true ? {} : mutationOptions?.meta),
|
||||
...getXRay("useLogout", preferLegacyKeys),
|
||||
},
|
||||
});
|
||||
|
||||
const v3LegacyAuthProviderCompatibleMutation = useMutation<
|
||||
TLogoutData,
|
||||
Error | RefineError,
|
||||
(TVariables & Variables) | void,
|
||||
unknown
|
||||
>({
|
||||
mutationKey: [
|
||||
...keys().auth().action("logout").get(preferLegacyKeys),
|
||||
"v3LegacyAuthProviderCompatible",
|
||||
],
|
||||
mutationFn: legacyLogoutFromContext,
|
||||
onSuccess: async (data, variables) => {
|
||||
const redirectPath = variables?.redirectPath ?? data;
|
||||
|
||||
if (redirectPath === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (redirectPath) {
|
||||
if (routerType === "legacy") {
|
||||
push(redirectPath);
|
||||
} else {
|
||||
go({ to: redirectPath });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (routerType === "legacy") {
|
||||
push("/login");
|
||||
} else {
|
||||
go({ to: "/login" });
|
||||
}
|
||||
|
||||
await invalidateAuthStore();
|
||||
},
|
||||
onError: (error: any) => {
|
||||
open?.(buildNotification(error));
|
||||
},
|
||||
...(v3LegacyAuthProviderCompatible ? mutationOptions : {}),
|
||||
meta: {
|
||||
...(v3LegacyAuthProviderCompatible ? mutationOptions?.meta : {}),
|
||||
...getXRay("useLogout", preferLegacyKeys),
|
||||
},
|
||||
});
|
||||
|
||||
return v3LegacyAuthProviderCompatible
|
||||
? v3LegacyAuthProviderCompatibleMutation
|
||||
: mutation;
|
||||
}
|
||||
|
||||
const buildNotification = (
|
||||
error?: Error | RefineError,
|
||||
): OpenNotificationParams => {
|
||||
return {
|
||||
key: "useLogout-error",
|
||||
type: "error",
|
||||
message: error?.name || "Logout Error",
|
||||
description: error?.message || "Something went wrong during logout",
|
||||
};
|
||||
};
|
||||
|
||||
const buildSuccessNotification = (
|
||||
successNotification: SuccessNotificationResponse,
|
||||
): OpenNotificationParams => {
|
||||
return {
|
||||
message: successNotification.message,
|
||||
description: successNotification.description,
|
||||
key: "logout-success",
|
||||
type: "success",
|
||||
};
|
||||
};
|
||||
304
packages/core/src/hooks/auth/useOnError/index.spec.ts
Normal file
304
packages/core/src/hooks/auth/useOnError/index.spec.ts
Normal file
@@ -0,0 +1,304 @@
|
||||
import { renderHook, waitFor } from "@testing-library/react";
|
||||
|
||||
import {
|
||||
TestWrapper,
|
||||
act,
|
||||
mockLegacyRouterProvider,
|
||||
mockRouterProvider,
|
||||
} from "@test";
|
||||
|
||||
import { useOnError } from ".";
|
||||
import type { LegacyRouterProvider } from "../../../contexts/router/legacy/types";
|
||||
|
||||
const mockReplace = jest.fn();
|
||||
const mockPush = jest.fn();
|
||||
|
||||
const legacyRouterProvider: LegacyRouterProvider = {
|
||||
...mockLegacyRouterProvider(),
|
||||
useHistory: () => ({
|
||||
goBack: jest.fn(),
|
||||
replace: mockReplace,
|
||||
push: mockPush,
|
||||
}),
|
||||
};
|
||||
// NOTE : Will be removed in v5
|
||||
describe("v3LegacyAuthProviderCompatible useOnError Hook", () => {
|
||||
beforeEach(() => {
|
||||
mockReplace.mockReset();
|
||||
mockPush.mockReset();
|
||||
|
||||
jest.spyOn(console, "error").mockImplementation((message) => {
|
||||
if (message === "rejected" || message === "/customPath") return;
|
||||
console.warn(message);
|
||||
});
|
||||
});
|
||||
|
||||
it("logout and redirect to login if check error rejected", async () => {
|
||||
const onErrorMock = jest.fn();
|
||||
|
||||
const { result } = renderHook(
|
||||
() => useOnError({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
isProvided: true,
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.reject(),
|
||||
getPermissions: () => Promise.resolve(),
|
||||
logout: onErrorMock,
|
||||
getUserIdentity: () => Promise.resolve(),
|
||||
},
|
||||
legacyRouterProvider,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const { mutate: checkError } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
await checkError({});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(!result.current.isLoading).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(onErrorMock).toBeCalledTimes(1);
|
||||
expect(mockPush).toBeCalledWith("/login");
|
||||
});
|
||||
|
||||
it("logout and redirect to custom path if check error rejected", async () => {
|
||||
const { result } = renderHook(
|
||||
() => useOnError({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
isProvided: true,
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.reject("/customPath"),
|
||||
getPermissions: () => Promise.resolve(),
|
||||
logout: ({ redirectPath }) => {
|
||||
return Promise.resolve(redirectPath);
|
||||
},
|
||||
getUserIdentity: () => Promise.resolve(),
|
||||
},
|
||||
legacyRouterProvider,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const { mutate: checkError } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
await checkError({});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(!result.current.isLoading).toBeTruthy();
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
expect(mockPush).toBeCalledWith("/customPath");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("useOnError Hook", () => {
|
||||
beforeEach(() => {
|
||||
mockReplace.mockReset();
|
||||
mockPush.mockReset();
|
||||
|
||||
jest.spyOn(console, "error").mockImplementation((message) => {
|
||||
if (message === "rejected" || message === "/customPath") return;
|
||||
console.warn(message);
|
||||
});
|
||||
});
|
||||
|
||||
it("logout and redirect to given path if check error rejected", async () => {
|
||||
const { result } = renderHook(() => useOnError(), {
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
login: () => Promise.resolve({ success: true }),
|
||||
check: () => Promise.resolve({ authenticated: true }),
|
||||
onError: () =>
|
||||
Promise.resolve({
|
||||
error: new Error("rejected"),
|
||||
redirectTo: "/login",
|
||||
logout: true,
|
||||
}),
|
||||
getPermissions: () => Promise.resolve(),
|
||||
logout: () => Promise.resolve({ success: true }),
|
||||
},
|
||||
legacyRouterProvider,
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: checkError } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
await checkError({});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(!result.current.isLoading).toBeTruthy();
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
expect(mockPush).toBeCalledWith("/login");
|
||||
});
|
||||
});
|
||||
|
||||
it("not logout and redirect to given path if check error rejected with legacyRouterProvider", async () => {
|
||||
const { result } = renderHook(() => useOnError(), {
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
login: () => Promise.resolve({ success: true }),
|
||||
check: () => Promise.resolve({ authenticated: true }),
|
||||
onError: () =>
|
||||
Promise.resolve({
|
||||
error: new Error("rejected"),
|
||||
redirectTo: "/login",
|
||||
logout: false,
|
||||
}),
|
||||
getPermissions: () => Promise.resolve(),
|
||||
logout: () => Promise.resolve({ success: true }),
|
||||
},
|
||||
legacyRouterProvider,
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: checkError } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
await checkError({});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(!result.current.isLoading).toBeTruthy();
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
expect(mockReplace).toBeCalledWith("/login");
|
||||
});
|
||||
});
|
||||
|
||||
it("not logout and redirect to given path if check error rejected", async () => {
|
||||
const mockGo = jest.fn();
|
||||
const { result } = renderHook(() => useOnError(), {
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
login: () => Promise.resolve({ success: true }),
|
||||
check: () => Promise.resolve({ authenticated: true }),
|
||||
onError: () =>
|
||||
Promise.resolve({
|
||||
error: new Error("rejected"),
|
||||
redirectTo: "/login",
|
||||
logout: false,
|
||||
}),
|
||||
getPermissions: () => Promise.resolve(),
|
||||
logout: () => Promise.resolve({ success: true }),
|
||||
},
|
||||
routerProvider: mockRouterProvider({
|
||||
fns: {
|
||||
go: () => mockGo,
|
||||
},
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: checkError } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
await checkError({});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(!result.current.isLoading).toBeTruthy();
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
expect(mockGo).toBeCalledWith({
|
||||
to: "/login",
|
||||
type: "replace",
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// NOTE : Will be removed in v5
|
||||
describe("useOnError Hook authProvider selection", () => {
|
||||
it("selects new authProvider", async () => {
|
||||
const legacyCheckErrorMock = jest.fn(() => Promise.resolve());
|
||||
const onErrorMock = jest.fn(() => Promise.resolve({}));
|
||||
|
||||
const { result } = renderHook(() => useOnError(), {
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => legacyCheckErrorMock(),
|
||||
logout: () => Promise.resolve(),
|
||||
},
|
||||
authProvider: {
|
||||
login: () => Promise.resolve({ success: true }),
|
||||
check: () => Promise.resolve({ authenticated: true }),
|
||||
onError: () => onErrorMock(),
|
||||
logout: () => Promise.resolve({ success: true }),
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: login } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
login({});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBeFalsy();
|
||||
});
|
||||
|
||||
expect(legacyCheckErrorMock).not.toHaveBeenCalled();
|
||||
expect(onErrorMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("selects v3LegacyAuthProviderCompatible authProvider", async () => {
|
||||
const legacyCheckErrorMock = jest.fn(() => Promise.resolve());
|
||||
const onErrorMock = jest.fn(() => Promise.resolve({}));
|
||||
|
||||
const { result } = renderHook(
|
||||
() => useOnError({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => legacyCheckErrorMock(),
|
||||
logout: () => Promise.resolve(),
|
||||
},
|
||||
authProvider: {
|
||||
login: () => Promise.resolve({ success: true }),
|
||||
check: () => Promise.resolve({ authenticated: true }),
|
||||
onError: () => onErrorMock(),
|
||||
logout: () => Promise.resolve({ success: true }),
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const { mutate: login } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
login({});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBeFalsy();
|
||||
});
|
||||
|
||||
expect(legacyCheckErrorMock).toHaveBeenCalled();
|
||||
expect(onErrorMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
130
packages/core/src/hooks/auth/useOnError/index.ts
Normal file
130
packages/core/src/hooks/auth/useOnError/index.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import { getXRay } from "@refinedev/devtools-internal";
|
||||
import { type UseMutationResult, useMutation } from "@tanstack/react-query";
|
||||
|
||||
import { useAuthBindingsContext, useLegacyAuthContext } from "@contexts/auth";
|
||||
import { useGo, useLogout, useNavigation, useRouterType } from "@hooks";
|
||||
import { useKeys } from "@hooks/useKeys";
|
||||
|
||||
import type { OnErrorResponse } from "../../../contexts/auth/types";
|
||||
|
||||
export type UseOnErrorLegacyProps = {
|
||||
v3LegacyAuthProviderCompatible: true;
|
||||
};
|
||||
|
||||
export type UseOnErrorProps = {
|
||||
v3LegacyAuthProviderCompatible?: false;
|
||||
};
|
||||
|
||||
export type UseOnErrorCombinedProps = {
|
||||
v3LegacyAuthProviderCompatible: boolean;
|
||||
};
|
||||
|
||||
export type UseOnErrorLegacyReturnType = UseMutationResult<
|
||||
void,
|
||||
string | undefined,
|
||||
any,
|
||||
unknown
|
||||
>;
|
||||
|
||||
export type UseOnErrorReturnType = UseMutationResult<
|
||||
OnErrorResponse,
|
||||
unknown,
|
||||
unknown,
|
||||
unknown
|
||||
>;
|
||||
export type UseOnErrorCombinedReturnType = UseMutationResult<
|
||||
OnErrorResponse | void,
|
||||
unknown,
|
||||
unknown,
|
||||
unknown
|
||||
>;
|
||||
|
||||
export function useOnError(
|
||||
props: UseOnErrorLegacyProps,
|
||||
): UseOnErrorLegacyReturnType;
|
||||
|
||||
export function useOnError(props?: UseOnErrorProps): UseOnErrorReturnType;
|
||||
|
||||
export function useOnError(
|
||||
props?: UseOnErrorCombinedProps,
|
||||
): UseOnErrorCombinedReturnType;
|
||||
|
||||
/**
|
||||
* `useOnError` calls the `checkError` method from the {@link https://refine.dev/docs/core/providers/auth-provider `authProvider`} under the hood.
|
||||
*
|
||||
* @see {@link https://refine.dev/docs/api-reference/core/hooks/auth/useCheckError} for more details.
|
||||
*
|
||||
*/
|
||||
export function useOnError({
|
||||
v3LegacyAuthProviderCompatible = false,
|
||||
}: UseOnErrorProps | UseOnErrorLegacyProps = {}):
|
||||
| UseOnErrorReturnType
|
||||
| UseOnErrorLegacyReturnType {
|
||||
const routerType = useRouterType();
|
||||
const go = useGo();
|
||||
const { replace } = useNavigation();
|
||||
|
||||
const { checkError: legacyCheckErrorFromContext } = useLegacyAuthContext();
|
||||
const { onError: onErrorFromContext } = useAuthBindingsContext();
|
||||
|
||||
const { keys, preferLegacyKeys } = useKeys();
|
||||
|
||||
const { mutate: legacyLogout } = useLogout({
|
||||
v3LegacyAuthProviderCompatible: Boolean(v3LegacyAuthProviderCompatible),
|
||||
});
|
||||
const { mutate: logout } = useLogout({
|
||||
v3LegacyAuthProviderCompatible: Boolean(v3LegacyAuthProviderCompatible),
|
||||
});
|
||||
|
||||
const mutation = useMutation<OnErrorResponse, unknown, unknown, unknown>({
|
||||
mutationKey: keys().auth().action("onError").get(preferLegacyKeys),
|
||||
...(onErrorFromContext
|
||||
? {
|
||||
mutationFn: onErrorFromContext,
|
||||
onSuccess: ({ logout: shouldLogout, redirectTo }) => {
|
||||
if (shouldLogout) {
|
||||
logout({ redirectPath: redirectTo });
|
||||
return;
|
||||
}
|
||||
|
||||
if (redirectTo) {
|
||||
if (routerType === "legacy") {
|
||||
replace(redirectTo);
|
||||
} else {
|
||||
go({ to: redirectTo, type: "replace" });
|
||||
}
|
||||
return;
|
||||
}
|
||||
},
|
||||
}
|
||||
: {
|
||||
mutationFn: () => ({}) as Promise<OnErrorResponse>,
|
||||
}),
|
||||
meta: {
|
||||
...getXRay("useOnError", preferLegacyKeys),
|
||||
},
|
||||
});
|
||||
|
||||
const v3LegacyAuthProviderCompatibleMutation = useMutation({
|
||||
mutationKey: [
|
||||
...keys().auth().action("onError").get(preferLegacyKeys),
|
||||
"v3LegacyAuthProviderCompatible",
|
||||
],
|
||||
mutationFn: legacyCheckErrorFromContext,
|
||||
onError: (redirectPath?: string) => {
|
||||
legacyLogout({ redirectPath });
|
||||
},
|
||||
meta: {
|
||||
...getXRay("useOnError", preferLegacyKeys),
|
||||
},
|
||||
});
|
||||
|
||||
return v3LegacyAuthProviderCompatible
|
||||
? v3LegacyAuthProviderCompatibleMutation
|
||||
: mutation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated `useCheckError` is deprecated with refine@4, use `useOnError` instead, however, we still support `useCheckError` for backward compatibility.
|
||||
*/
|
||||
export const useCheckError = useOnError;
|
||||
270
packages/core/src/hooks/auth/usePermissions/index.spec.ts
Normal file
270
packages/core/src/hooks/auth/usePermissions/index.spec.ts
Normal file
@@ -0,0 +1,270 @@
|
||||
import { renderHook, waitFor } from "@testing-library/react";
|
||||
|
||||
import { MockJSONServer, TestWrapper } from "@test";
|
||||
|
||||
import { usePermissions } from "./";
|
||||
|
||||
describe("usePermissions Hook", () => {
|
||||
it("returns authenticated userPermissions", async () => {
|
||||
const { result } = renderHook(() => usePermissions(), {
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
login: () => Promise.resolve({ success: true }),
|
||||
check: () => Promise.resolve({ authenticated: true }),
|
||||
onError: () => Promise.resolve({}),
|
||||
logout: () => Promise.resolve({ success: true }),
|
||||
getPermissions: () => Promise.resolve(["admin"]),
|
||||
},
|
||||
dataProvider: MockJSONServer,
|
||||
resources: [{ name: "posts" }],
|
||||
}),
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(result.current.data).toEqual(["admin"]);
|
||||
});
|
||||
|
||||
it("returns error for not authenticated", async () => {
|
||||
jest.spyOn(console, "error").mockImplementation((message) => {
|
||||
if (!message.includes("Not Authenticated")) console.warn(message);
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => usePermissions(), {
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
login: () => Promise.resolve({ success: false }),
|
||||
check: () => Promise.resolve({ authenticated: false }),
|
||||
onError: () => Promise.resolve({}),
|
||||
logout: () => Promise.resolve({ success: false }),
|
||||
getPermissions: () => Promise.resolve("Not Authenticated"),
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBeFalsy();
|
||||
});
|
||||
|
||||
expect(result.current.data).toEqual("Not Authenticated");
|
||||
});
|
||||
|
||||
it("should resolve undefined if no authProvider is provided", async () => {
|
||||
const { result } = renderHook(() => usePermissions(), {
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
getPermissions: undefined,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
result.current.refetch();
|
||||
expect(result.current.data).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should accept params", async () => {
|
||||
const mockGetPermissions = jest.fn(() => Promise.resolve(["admin"]));
|
||||
const { result } = renderHook((props) => usePermissions({ ...props }), {
|
||||
initialProps: {
|
||||
params: { currentRole: "admin" },
|
||||
v3LegacyAuthProviderCompatible: false,
|
||||
},
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
login: () => Promise.resolve({ success: true }),
|
||||
check: () => Promise.resolve({ authenticated: true }),
|
||||
onError: () => Promise.resolve({}),
|
||||
logout: () => Promise.resolve({ success: true }),
|
||||
getPermissions: mockGetPermissions,
|
||||
},
|
||||
dataProvider: MockJSONServer,
|
||||
resources: [{ name: "posts" }],
|
||||
}),
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(mockGetPermissions).toHaveBeenCalledWith({ currentRole: "admin" });
|
||||
expect(result.current.data).toEqual(["admin"]);
|
||||
});
|
||||
});
|
||||
|
||||
// NOTE : Will be removed in v5
|
||||
describe("v3LegacyAuthProviderCompatible usePermissions Hook", () => {
|
||||
it("returns authenticated userPermissions", async () => {
|
||||
const getPermissionMock = jest.fn(() => Promise.resolve(["admin"]));
|
||||
|
||||
const { result } = renderHook(
|
||||
() => usePermissions({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
getPermissions: getPermissionMock,
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBeFalsy();
|
||||
});
|
||||
expect(getPermissionMock).toHaveBeenCalledTimes(1);
|
||||
expect(result.current.data).toEqual(["admin"]);
|
||||
|
||||
result.current.refetch();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBeFalsy();
|
||||
});
|
||||
expect(getPermissionMock).toHaveBeenCalledTimes(2);
|
||||
expect(result.current.data).toEqual(["admin"]);
|
||||
});
|
||||
it("returns error for not authenticated", async () => {
|
||||
jest.spyOn(console, "error").mockImplementation((message) => {
|
||||
if (!message.includes("Not Authenticated")) console.warn(message);
|
||||
});
|
||||
|
||||
const { result } = renderHook(
|
||||
() => usePermissions({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.reject("Not Authenticated"),
|
||||
checkError: () => Promise.resolve(),
|
||||
getPermissions: () => Promise.reject("Not Authenticated"),
|
||||
logout: () => Promise.resolve(),
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isError).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(result.current.error).toEqual("Not Authenticated");
|
||||
});
|
||||
|
||||
it("should resolve undefined if no authProvider is provided", async () => {
|
||||
const { result } = renderHook(
|
||||
() => usePermissions({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
getPermissions: undefined,
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
result.current.refetch();
|
||||
expect(result.current.data).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should accept params with v3LegacyAuthProviderCompatible", async () => {
|
||||
const legacyGetPermissionMock = jest.fn(() => Promise.resolve(["admin"]));
|
||||
const { result } = renderHook((props) => usePermissions({ ...props }), {
|
||||
initialProps: {
|
||||
params: { currentRole: "admin" },
|
||||
v3LegacyAuthProviderCompatible: true,
|
||||
},
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
getPermissions: legacyGetPermissionMock,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBeFalsy();
|
||||
});
|
||||
|
||||
expect(legacyGetPermissionMock).toHaveBeenCalledWith({
|
||||
currentRole: "admin",
|
||||
});
|
||||
expect(result.current.data).toEqual(["admin"]);
|
||||
});
|
||||
});
|
||||
|
||||
// NOTE : Will be removed in v5
|
||||
describe("usePermissions Hook authProvider selection", () => {
|
||||
it("selects new authProvider", async () => {
|
||||
const legacyGetPermissionMock = jest.fn(() => Promise.resolve());
|
||||
const getPermissionMock = jest.fn(() => Promise.resolve());
|
||||
|
||||
const { result } = renderHook(() => usePermissions(), {
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
getPermissions: () => legacyGetPermissionMock(),
|
||||
},
|
||||
authProvider: {
|
||||
login: () => Promise.resolve({ success: true }),
|
||||
check: () => Promise.resolve({ authenticated: true }),
|
||||
onError: () => Promise.resolve({}),
|
||||
logout: () => Promise.resolve({ success: true }),
|
||||
getPermissions: () => getPermissionMock(),
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBeFalsy();
|
||||
});
|
||||
|
||||
expect(legacyGetPermissionMock).not.toHaveBeenCalled();
|
||||
expect(getPermissionMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("selects v3LegacyAuthProviderCompatible authProvider", async () => {
|
||||
const legacyGetPermissionMock = jest.fn(() => Promise.resolve());
|
||||
const getPermissionMock = jest.fn(() => Promise.resolve());
|
||||
|
||||
const { result } = renderHook(
|
||||
() => usePermissions({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
getPermissions: () => legacyGetPermissionMock(),
|
||||
},
|
||||
authProvider: {
|
||||
login: () => Promise.resolve({ success: true }),
|
||||
check: () => Promise.resolve({ authenticated: true }),
|
||||
onError: () => Promise.resolve({}),
|
||||
logout: () => Promise.resolve({ success: true }),
|
||||
getPermissions: () => getPermissionMock(),
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBeFalsy();
|
||||
});
|
||||
|
||||
expect(legacyGetPermissionMock).toHaveBeenCalled();
|
||||
expect(getPermissionMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
133
packages/core/src/hooks/auth/usePermissions/index.ts
Normal file
133
packages/core/src/hooks/auth/usePermissions/index.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import { getXRay } from "@refinedev/devtools-internal";
|
||||
import {
|
||||
type UseQueryOptions,
|
||||
type UseQueryResult,
|
||||
useQuery,
|
||||
} from "@tanstack/react-query";
|
||||
|
||||
import { useAuthBindingsContext, useLegacyAuthContext } from "@contexts/auth";
|
||||
import { useKeys } from "@hooks/useKeys";
|
||||
|
||||
import type { PermissionResponse } from "../../../contexts/auth/types";
|
||||
|
||||
export type UsePermissionsLegacyProps<
|
||||
TData = any,
|
||||
TParams extends Record<string, any> = Record<string, any>,
|
||||
> = {
|
||||
v3LegacyAuthProviderCompatible: true;
|
||||
options?: UseQueryOptions<TData>;
|
||||
params?: TParams;
|
||||
};
|
||||
|
||||
export type UsePermissionsProps<
|
||||
TData = PermissionResponse,
|
||||
TParams extends Record<string, any> = Record<string, any>,
|
||||
> = {
|
||||
v3LegacyAuthProviderCompatible?: false;
|
||||
options?: UseQueryOptions<TData>;
|
||||
params?: TParams;
|
||||
};
|
||||
|
||||
export type UsePermissionsCombinedProps<
|
||||
TData = any,
|
||||
TParams extends Record<string, any> = Record<string, any>,
|
||||
> = {
|
||||
v3LegacyAuthProviderCompatible: boolean;
|
||||
options?: UseQueryOptions<TData> | UseQueryOptions<PermissionResponse>;
|
||||
params?: TParams;
|
||||
};
|
||||
|
||||
export type UsePermissionsLegacyReturnType<TData = any> = UseQueryResult<
|
||||
TData,
|
||||
unknown
|
||||
>;
|
||||
|
||||
export type UsePermissionsReturnType<TData = PermissionResponse> =
|
||||
UseQueryResult<TData, unknown>;
|
||||
|
||||
export type UsePermissionsCombinedReturnType<TData = any> =
|
||||
| UseQueryResult<TData, unknown>
|
||||
| UseQueryResult<PermissionResponse, unknown>;
|
||||
|
||||
export function usePermissions<
|
||||
TData = any,
|
||||
TParams extends Record<string, any> = Record<string, any>,
|
||||
>(
|
||||
props: UsePermissionsLegacyProps<TData, TParams>,
|
||||
): UsePermissionsLegacyReturnType<TData>;
|
||||
|
||||
export function usePermissions<
|
||||
TData = PermissionResponse,
|
||||
TParams extends Record<string, any> = Record<string, any>,
|
||||
>(props?: UsePermissionsProps<TData, TParams>): UsePermissionsReturnType<TData>;
|
||||
|
||||
export function usePermissions<
|
||||
TData = any,
|
||||
TParams extends Record<string, any> = Record<string, any>,
|
||||
>(
|
||||
props?: UsePermissionsCombinedProps<TData, TParams>,
|
||||
): UsePermissionsCombinedReturnType<TData>;
|
||||
|
||||
/**
|
||||
* `usePermissions` calls the `getPermissions` method from the {@link https://refine.dev/docs/core/providers/auth-provider `authProvider`} under the hood.
|
||||
*
|
||||
* @see {@link https://refine.dev/docs/api-reference/core/hooks/auth/usePermissions} for more details.
|
||||
*
|
||||
* @typeParam TData - Result data of the query
|
||||
*
|
||||
* @typeParam TParams - Params will be passed to the `getPermissions` method of {@link https://refine.dev/docs/core/providers/auth-provider `authProvider`}.
|
||||
*
|
||||
*/
|
||||
export function usePermissions<
|
||||
TData = any,
|
||||
TParams extends Record<string, any> = Record<string, any>,
|
||||
>({
|
||||
v3LegacyAuthProviderCompatible = false,
|
||||
options,
|
||||
params,
|
||||
}:
|
||||
| UsePermissionsProps<TData, TParams>
|
||||
| UsePermissionsLegacyProps<TData, TParams> = {}):
|
||||
| UsePermissionsReturnType
|
||||
| UsePermissionsLegacyReturnType<TData> {
|
||||
const { getPermissions: legacyGetPermission } = useLegacyAuthContext();
|
||||
const { getPermissions } = useAuthBindingsContext();
|
||||
const { keys, preferLegacyKeys } = useKeys();
|
||||
|
||||
const queryResponse = useQuery<TData>({
|
||||
queryKey: keys().auth().action("permissions").get(preferLegacyKeys),
|
||||
// Enabled check for `getPermissions` is enough to be sure that it's defined in the query function but TS is not smart enough to know that.
|
||||
queryFn: (getPermissions
|
||||
? () => getPermissions(params)
|
||||
: () => Promise.resolve(undefined)) as (
|
||||
params?: unknown,
|
||||
) => Promise<TData>,
|
||||
enabled: !v3LegacyAuthProviderCompatible && !!getPermissions,
|
||||
...(v3LegacyAuthProviderCompatible ? {} : options),
|
||||
meta: {
|
||||
...(v3LegacyAuthProviderCompatible ? {} : options?.meta),
|
||||
...getXRay("usePermissions", preferLegacyKeys),
|
||||
},
|
||||
});
|
||||
|
||||
const legacyQueryResponse = useQuery<TData>({
|
||||
queryKey: [
|
||||
...keys().auth().action("permissions").get(preferLegacyKeys),
|
||||
"v3LegacyAuthProviderCompatible",
|
||||
],
|
||||
// Enabled check for `getPermissions` is enough to be sure that it's defined in the query function but TS is not smart enough to know that.
|
||||
queryFn: (legacyGetPermission
|
||||
? () => legacyGetPermission(params)
|
||||
: () => Promise.resolve(undefined)) as (
|
||||
params?: unknown,
|
||||
) => Promise<TData>,
|
||||
enabled: v3LegacyAuthProviderCompatible && !!legacyGetPermission,
|
||||
...(v3LegacyAuthProviderCompatible ? options : {}),
|
||||
meta: {
|
||||
...(v3LegacyAuthProviderCompatible ? options?.meta : {}),
|
||||
...getXRay("usePermissions", preferLegacyKeys),
|
||||
},
|
||||
});
|
||||
|
||||
return v3LegacyAuthProviderCompatible ? legacyQueryResponse : queryResponse;
|
||||
}
|
||||
821
packages/core/src/hooks/auth/useRegister/index.spec.ts
Normal file
821
packages/core/src/hooks/auth/useRegister/index.spec.ts
Normal file
@@ -0,0 +1,821 @@
|
||||
import { renderHook, waitFor } from "@testing-library/react";
|
||||
|
||||
import {
|
||||
TestWrapper,
|
||||
act,
|
||||
mockLegacyRouterProvider,
|
||||
mockRouterProvider,
|
||||
queryClient,
|
||||
} from "@test";
|
||||
|
||||
import { useRegister } from ".";
|
||||
|
||||
const mockGo = jest.fn();
|
||||
|
||||
const routerProvider = mockRouterProvider({
|
||||
fns: {
|
||||
go: () => mockGo,
|
||||
},
|
||||
});
|
||||
|
||||
// NOTE : Will be removed in v5
|
||||
describe("v3LegacyAuthProviderCompatible useRegister Hook", () => {
|
||||
beforeEach(() => {
|
||||
mockGo.mockReset();
|
||||
jest.spyOn(console, "error").mockImplementation((message) => {
|
||||
if (message?.message === "Missing fields") return;
|
||||
if (typeof message === "undefined") return;
|
||||
console.warn(message);
|
||||
});
|
||||
});
|
||||
|
||||
it("succeed register", async () => {
|
||||
const { result } = renderHook(
|
||||
() => useRegister({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
register: ({ email, password }) => {
|
||||
if (email && password) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(new Error("Missing fields"));
|
||||
},
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
getPermissions: () => Promise.resolve(),
|
||||
logout: () => Promise.resolve(),
|
||||
getUserIdentity: () => Promise.resolve({ id: 1 }),
|
||||
},
|
||||
routerProvider,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const { mutate: register } = result.current ?? { mutate: () => 0 };
|
||||
|
||||
await act(async () => {
|
||||
register({ email: "test", password: "test" });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(mockGo).toBeCalledWith({ to: "/", type: "replace" });
|
||||
});
|
||||
|
||||
it("should successfully register with no redirect", async () => {
|
||||
const { result } = renderHook(
|
||||
() => useRegister({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
register: ({ email, password }) => {
|
||||
if (email && password) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
return Promise.reject(new Error("Missing fields"));
|
||||
},
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
getPermissions: () => Promise.resolve(),
|
||||
logout: () => Promise.resolve(),
|
||||
getUserIdentity: () => Promise.resolve({ id: 1 }),
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const { mutate: register } = result.current ?? { mutate: () => 0 };
|
||||
|
||||
await act(async () => {
|
||||
register({ email: "test", password: "test" });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(mockGo).not.toBeCalled();
|
||||
});
|
||||
|
||||
it("fail register", async () => {
|
||||
const { result } = renderHook(
|
||||
() => useRegister({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
register: () => Promise.reject(new Error("Missing fields")),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
getPermissions: () => Promise.resolve(),
|
||||
logout: () => Promise.resolve(),
|
||||
getUserIdentity: () => Promise.resolve({ id: 1 }),
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const { mutate: register } = result.current ?? { mutate: () => 0 };
|
||||
|
||||
await act(async () => {
|
||||
register({ email: "demo" });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isError).toBeTruthy();
|
||||
});
|
||||
|
||||
const { error } = result.current ?? { error: undefined };
|
||||
|
||||
expect(error).toEqual(new Error("Missing fields"));
|
||||
});
|
||||
|
||||
it("register rejected with undefined error", async () => {
|
||||
const { result } = renderHook(
|
||||
() => useRegister({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
register: () => Promise.reject(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
getPermissions: () => Promise.resolve(),
|
||||
logout: () => Promise.resolve(),
|
||||
getUserIdentity: () => Promise.resolve({ id: 1 }),
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const { mutate: register } = result.current ?? { mutate: () => 0 };
|
||||
|
||||
await act(async () => {
|
||||
register({ email: "demo" });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isError).toBeTruthy();
|
||||
});
|
||||
|
||||
const { error } = result.current ?? { error: undefined };
|
||||
|
||||
expect(error).not.toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("useRegister Hook", () => {
|
||||
const mockAuthProvider = {
|
||||
login: () => Promise.resolve({ success: true }),
|
||||
check: () => Promise.resolve({ authenticated: true }),
|
||||
logout: () => Promise.resolve({ success: true }),
|
||||
onError: () => Promise.resolve({}),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockGo.mockReset();
|
||||
|
||||
jest.spyOn(console, "error").mockImplementation((message) => {
|
||||
if (message?.message === "Missing fields") return;
|
||||
if (typeof message === "undefined") return;
|
||||
console.warn(message);
|
||||
});
|
||||
});
|
||||
|
||||
it("succeed register", async () => {
|
||||
const { result } = renderHook(() => useRegister(), {
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
register: ({ email, password }: any) => {
|
||||
if (email && password) {
|
||||
return Promise.resolve({
|
||||
success: true,
|
||||
redirectTo: "/",
|
||||
});
|
||||
}
|
||||
return Promise.resolve({
|
||||
success: false,
|
||||
error: new Error("Missing fields"),
|
||||
});
|
||||
},
|
||||
},
|
||||
routerProvider,
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: register } = result.current ?? { mutate: () => 0 };
|
||||
|
||||
await act(async () => {
|
||||
register({ email: "test", password: "test" });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(result.current.data?.success).toBeTruthy();
|
||||
|
||||
expect(mockGo).toBeCalledWith({ to: "/", type: "replace" });
|
||||
});
|
||||
|
||||
it("should successfully register with no redirect", async () => {
|
||||
const { result } = renderHook(() => useRegister(), {
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
register: ({ email, password }: any) => {
|
||||
if (email && password) {
|
||||
return Promise.resolve({
|
||||
success: true,
|
||||
});
|
||||
}
|
||||
return Promise.resolve({
|
||||
success: false,
|
||||
error: new Error("Missing fields"),
|
||||
});
|
||||
},
|
||||
},
|
||||
routerProvider,
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: register } = result.current ?? { mutate: () => 0 };
|
||||
|
||||
await act(async () => {
|
||||
register({ email: "test", password: "test" });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(mockGo).not.toBeCalled();
|
||||
});
|
||||
|
||||
it("should successfully register with redirect on legacyRouterProvider", async () => {
|
||||
const mockReplace = jest.fn();
|
||||
const { result } = renderHook(() => useRegister(), {
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
register: ({ email, password }: any) => {
|
||||
if (email && password) {
|
||||
return Promise.resolve({
|
||||
success: true,
|
||||
redirectTo: "redirectTo",
|
||||
});
|
||||
}
|
||||
return Promise.resolve({
|
||||
success: false,
|
||||
error: new Error("Missing fields"),
|
||||
});
|
||||
},
|
||||
},
|
||||
legacyRouterProvider: {
|
||||
...mockLegacyRouterProvider(),
|
||||
useHistory: () => ({
|
||||
goBack: jest.fn(),
|
||||
replace: mockReplace,
|
||||
push: jest.fn(),
|
||||
}),
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: register } = result.current ?? { mutate: () => 0 };
|
||||
|
||||
await act(async () => {
|
||||
register({ email: "test", password: "test" });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(mockReplace).toBeCalledWith("redirectTo");
|
||||
});
|
||||
|
||||
it("should successfully register without redirect on legacyRouterProvider", async () => {
|
||||
const mockReplace = jest.fn();
|
||||
const { result } = renderHook(() => useRegister(), {
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
register: ({ email, password }: any) => {
|
||||
if (email && password) {
|
||||
return Promise.resolve({
|
||||
success: true,
|
||||
});
|
||||
}
|
||||
return Promise.resolve({
|
||||
success: false,
|
||||
error: new Error("Missing fields"),
|
||||
});
|
||||
},
|
||||
},
|
||||
legacyRouterProvider: {
|
||||
...mockLegacyRouterProvider(),
|
||||
useHistory: () => ({
|
||||
goBack: jest.fn(),
|
||||
replace: mockReplace,
|
||||
push: jest.fn(),
|
||||
}),
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: register } = result.current ?? { mutate: () => 0 };
|
||||
|
||||
await act(async () => {
|
||||
register({ email: "test", password: "test" });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(mockReplace).toBeCalledWith("/");
|
||||
});
|
||||
|
||||
it("fail register", async () => {
|
||||
const { result } = renderHook(() => useRegister(), {
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
register: ({ email, password }: any) => {
|
||||
if (email && password) {
|
||||
return Promise.resolve({
|
||||
success: true,
|
||||
});
|
||||
}
|
||||
return Promise.resolve({
|
||||
success: false,
|
||||
error: new Error("Missing fields"),
|
||||
});
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: register } = result.current ?? { mutate: () => 0 };
|
||||
|
||||
await act(async () => {
|
||||
register({ email: "demo" });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(result.current.data).toEqual({
|
||||
success: false,
|
||||
error: new Error("Missing fields"),
|
||||
});
|
||||
});
|
||||
|
||||
it("register rejected with undefined error", async () => {
|
||||
const { result } = renderHook(() => useRegister(), {
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
register: ({ email, password }: any) => {
|
||||
if (email && password) {
|
||||
return Promise.resolve({
|
||||
success: true,
|
||||
});
|
||||
}
|
||||
return Promise.resolve({
|
||||
success: false,
|
||||
});
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: register } = result.current ?? { mutate: () => 0 };
|
||||
|
||||
await act(async () => {
|
||||
register({ email: "demo" });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(result.current.data?.error).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should open notification when has error is true", async () => {
|
||||
const openNotificationMock = jest.fn();
|
||||
|
||||
const { result } = renderHook(() => useRegister(), {
|
||||
wrapper: TestWrapper({
|
||||
notificationProvider: {
|
||||
open: openNotificationMock,
|
||||
close: jest.fn(),
|
||||
},
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
register: () => {
|
||||
return Promise.resolve({
|
||||
success: false,
|
||||
error: new Error("Error"),
|
||||
});
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: forgotPassword } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
forgotPassword({});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(openNotificationMock).toBeCalledWith({
|
||||
key: "register-error",
|
||||
type: "error",
|
||||
message: "Error",
|
||||
description: "Error",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should open notification when has success is false, error is undefined", async () => {
|
||||
const openNotificationMock = jest.fn();
|
||||
|
||||
const { result } = renderHook(() => useRegister(), {
|
||||
wrapper: TestWrapper({
|
||||
notificationProvider: {
|
||||
open: openNotificationMock,
|
||||
close: jest.fn(),
|
||||
},
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
register: () => Promise.resolve({ success: false }),
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: forgotPassword } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
forgotPassword({});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(openNotificationMock).toBeCalledWith({
|
||||
key: "register-error",
|
||||
type: "error",
|
||||
message: "Register Error",
|
||||
description: "Error while registering",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should open notification when throw error", async () => {
|
||||
const openNotificationMock = jest.fn();
|
||||
|
||||
const { result } = renderHook(() => useRegister(), {
|
||||
wrapper: TestWrapper({
|
||||
notificationProvider: {
|
||||
open: openNotificationMock,
|
||||
close: jest.fn(),
|
||||
},
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
register: () => {
|
||||
throw new Error("Unhandled error");
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: forgotPassword } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
forgotPassword({});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(openNotificationMock).toBeCalledWith({
|
||||
key: "register-error",
|
||||
type: "error",
|
||||
message: "Error",
|
||||
description: "Unhandled error",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should override `mutationFn` with mutationOptions.mutationFn", async () => {
|
||||
const registerMock = jest.fn().mockResolvedValue({ data: {} });
|
||||
const mutationFnMock = jest.fn().mockResolvedValue({ data: {} });
|
||||
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useRegister({
|
||||
mutationOptions: {
|
||||
// mutationFn is omitted in types. So we need to use @ts-ignore test it.
|
||||
// @ts-ignore
|
||||
mutationFn: mutationFnMock,
|
||||
},
|
||||
}),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
register: registerMock,
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
result.current.mutate({});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(registerMock).not.toBeCalled();
|
||||
expect(mutationFnMock).toBeCalled();
|
||||
});
|
||||
|
||||
it("should override `mutationKey` with `mutationOptions.mutationKey`", async () => {
|
||||
const registerMock = jest.fn().mockResolvedValue({ data: {} });
|
||||
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useRegister({
|
||||
mutationOptions: {
|
||||
mutationKey: ["foo", "bar"],
|
||||
},
|
||||
}),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
register: registerMock,
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
result.current.mutate({});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(
|
||||
queryClient.getMutationCache().findAll({
|
||||
mutationKey: ["foo", "bar"],
|
||||
}),
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("should open success notification when successNotification is passed", async () => {
|
||||
const openNotificationMock = jest.fn();
|
||||
|
||||
const successNotification = {
|
||||
message: "Success!",
|
||||
description: "Operation completed successfully",
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useRegister(), {
|
||||
wrapper: TestWrapper({
|
||||
notificationProvider: {
|
||||
open: openNotificationMock,
|
||||
close: jest.fn(),
|
||||
},
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
register: () =>
|
||||
Promise.resolve({
|
||||
success: true,
|
||||
successNotification,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
result.current.mutate({});
|
||||
});
|
||||
|
||||
expect(openNotificationMock).toHaveBeenCalledWith({
|
||||
key: "register-success",
|
||||
type: "success",
|
||||
message: "Success!",
|
||||
description: "Operation completed successfully",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// NOTE : Will be removed in v5
|
||||
describe("useRegister Hook authProvider selection", () => {
|
||||
it("selects new authProvider", async () => {
|
||||
const legacyRegisterMock = jest.fn(() => Promise.resolve());
|
||||
const registerMock = jest.fn(() => Promise.resolve({ success: true }));
|
||||
|
||||
const { result } = renderHook(() => useRegister(), {
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
logout: () => Promise.resolve(),
|
||||
register: () => legacyRegisterMock(),
|
||||
},
|
||||
authProvider: {
|
||||
login: () => Promise.resolve({ success: true }),
|
||||
check: () => Promise.resolve({ authenticated: true }),
|
||||
onError: () => Promise.resolve({}),
|
||||
logout: () => Promise.resolve({ success: true }),
|
||||
register: () => registerMock(),
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: login } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
login({});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBeFalsy();
|
||||
});
|
||||
|
||||
expect(legacyRegisterMock).not.toHaveBeenCalled();
|
||||
expect(registerMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("selects v3LegacyAuthProviderCompatible authProvider", async () => {
|
||||
const legacyRegisterMock = jest.fn(() => Promise.resolve());
|
||||
const registerMock = jest.fn(() => Promise.resolve({ success: true }));
|
||||
|
||||
const { result } = renderHook(
|
||||
() => useRegister({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
logout: () => Promise.resolve(),
|
||||
register: () => legacyRegisterMock(),
|
||||
},
|
||||
authProvider: {
|
||||
login: () => Promise.resolve({ success: true }),
|
||||
check: () => Promise.resolve({ authenticated: true }),
|
||||
onError: () => Promise.resolve({}),
|
||||
logout: () => Promise.resolve({ success: true }),
|
||||
register: () => registerMock(),
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const { mutate: login } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
login({});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBeFalsy();
|
||||
});
|
||||
|
||||
expect(legacyRegisterMock).toHaveBeenCalled();
|
||||
expect(registerMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("useRegister Hook use v3LegacyAuthProviderCompatible", () => {
|
||||
it("should successfully register with redirect on legacyRouterProvider", async () => {
|
||||
const mockReplace = jest.fn();
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useRegister({
|
||||
v3LegacyAuthProviderCompatible: true,
|
||||
}),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
logout: () => Promise.resolve(),
|
||||
register: () => Promise.resolve("redirectTo"),
|
||||
},
|
||||
legacyRouterProvider: {
|
||||
...mockLegacyRouterProvider(),
|
||||
useHistory: () => ({
|
||||
goBack: jest.fn(),
|
||||
replace: mockReplace,
|
||||
push: jest.fn(),
|
||||
}),
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const { mutate: register } = result.current ?? { mutate: () => 0 };
|
||||
|
||||
await act(async () => {
|
||||
register({ email: "test", password: "test" });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(mockReplace).toBeCalledWith("redirectTo");
|
||||
});
|
||||
|
||||
it("should successfully register without redirect on legacyRouterProvider", async () => {
|
||||
const mockReplace = jest.fn();
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useRegister({
|
||||
v3LegacyAuthProviderCompatible: true,
|
||||
}),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
logout: () => Promise.resolve(),
|
||||
register: () => Promise.resolve(),
|
||||
},
|
||||
legacyRouterProvider: {
|
||||
...mockLegacyRouterProvider(),
|
||||
useHistory: () => ({
|
||||
goBack: jest.fn(),
|
||||
replace: mockReplace,
|
||||
push: jest.fn(),
|
||||
}),
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const { mutate: register } = result.current ?? { mutate: () => 0 };
|
||||
|
||||
await act(async () => {
|
||||
register({ email: "test", password: "test" });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(mockReplace).toBeCalledWith("/");
|
||||
});
|
||||
|
||||
it("should successfully register with redirect", async () => {
|
||||
const mockGo = jest.fn();
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useRegister({
|
||||
v3LegacyAuthProviderCompatible: true,
|
||||
}),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
logout: () => Promise.resolve(),
|
||||
register: () => Promise.resolve("redirectTo"),
|
||||
},
|
||||
routerProvider: mockRouterProvider({
|
||||
fns: {
|
||||
go: () => mockGo,
|
||||
},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const { mutate: register } = result.current ?? { mutate: () => 0 };
|
||||
|
||||
await act(async () => {
|
||||
register({ email: "test", password: "test" });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(mockGo).toBeCalledWith({ to: "redirectTo", type: "replace" });
|
||||
});
|
||||
});
|
||||
231
packages/core/src/hooks/auth/useRegister/index.ts
Normal file
231
packages/core/src/hooks/auth/useRegister/index.ts
Normal file
@@ -0,0 +1,231 @@
|
||||
import { getXRay } from "@refinedev/devtools-internal";
|
||||
import {
|
||||
type UseMutationOptions,
|
||||
type UseMutationResult,
|
||||
useMutation,
|
||||
} from "@tanstack/react-query";
|
||||
|
||||
import { useAuthBindingsContext, useLegacyAuthContext } from "@contexts/auth";
|
||||
import {
|
||||
useGo,
|
||||
useKeys,
|
||||
useNavigation,
|
||||
useNotification,
|
||||
useRouterType,
|
||||
} from "@hooks";
|
||||
|
||||
import type {
|
||||
AuthActionResponse,
|
||||
SuccessNotificationResponse,
|
||||
TLoginData,
|
||||
TRegisterData,
|
||||
} from "../../../contexts/auth/types";
|
||||
import type { RefineError } from "../../../contexts/data/types";
|
||||
import type { OpenNotificationParams } from "../../../contexts/notification/types";
|
||||
import { useInvalidateAuthStore } from "../useInvalidateAuthStore";
|
||||
|
||||
export type UseRegisterLegacyProps<TVariables> = {
|
||||
v3LegacyAuthProviderCompatible: true;
|
||||
mutationOptions?: Omit<
|
||||
UseMutationOptions<TRegisterData, Error | RefineError, TVariables, unknown>,
|
||||
"mutationFn" | "onError" | "onSuccess"
|
||||
>;
|
||||
};
|
||||
|
||||
export type UseRegisterProps<TVariables> = {
|
||||
v3LegacyAuthProviderCompatible?: false;
|
||||
mutationOptions?: Omit<
|
||||
UseMutationOptions<
|
||||
AuthActionResponse,
|
||||
Error | RefineError,
|
||||
TVariables,
|
||||
unknown
|
||||
>,
|
||||
"mutationFn"
|
||||
>;
|
||||
};
|
||||
|
||||
export type UseRegisterCombinedProps<TVariables> = {
|
||||
v3LegacyAuthProviderCompatible: boolean;
|
||||
mutationOptions?: Omit<
|
||||
UseMutationOptions<
|
||||
AuthActionResponse | TRegisterData,
|
||||
Error | RefineError,
|
||||
TVariables,
|
||||
unknown
|
||||
>,
|
||||
"mutationFn"
|
||||
>;
|
||||
};
|
||||
|
||||
export type UseRegisterLegacyReturnType<TVariables> = UseMutationResult<
|
||||
TRegisterData,
|
||||
Error | RefineError,
|
||||
TVariables,
|
||||
unknown
|
||||
>;
|
||||
|
||||
export type UseRegisterReturnType<TVariables> = UseMutationResult<
|
||||
AuthActionResponse,
|
||||
Error | RefineError,
|
||||
TVariables,
|
||||
unknown
|
||||
>;
|
||||
|
||||
export type UseRegisterCombinedReturnType<TVariables> = UseMutationResult<
|
||||
AuthActionResponse | TLoginData,
|
||||
Error | RefineError,
|
||||
TVariables,
|
||||
unknown
|
||||
>;
|
||||
|
||||
export function useRegister<TVariables = {}>(
|
||||
props: UseRegisterLegacyProps<TVariables>,
|
||||
): UseRegisterLegacyReturnType<TVariables>;
|
||||
|
||||
export function useRegister<TVariables = {}>(
|
||||
props?: UseRegisterProps<TVariables>,
|
||||
): UseRegisterReturnType<TVariables>;
|
||||
|
||||
export function useRegister<TVariables = {}>(
|
||||
props?: UseRegisterCombinedProps<TVariables>,
|
||||
): UseRegisterCombinedReturnType<TVariables>;
|
||||
|
||||
/**
|
||||
* `useRegister` calls `register` method from {@link https://refine.dev/docs/api-reference/core/providers/auth-provider `authProvider`} under the hood.
|
||||
*
|
||||
* @see {@link https://refine.dev/docs/api-reference/core/hooks/auth/useRegister} for more details.
|
||||
*
|
||||
* @typeParam TData - Result data of the query
|
||||
* @typeParam TVariables - Values for mutation function. default `{}`
|
||||
*
|
||||
*/
|
||||
export function useRegister<TVariables = {}>({
|
||||
v3LegacyAuthProviderCompatible,
|
||||
mutationOptions,
|
||||
}: UseRegisterProps<TVariables> | UseRegisterLegacyProps<TVariables> = {}):
|
||||
| UseRegisterReturnType<TVariables>
|
||||
| UseRegisterLegacyReturnType<TVariables> {
|
||||
const invalidateAuthStore = useInvalidateAuthStore();
|
||||
const routerType = useRouterType();
|
||||
const go = useGo();
|
||||
const { replace } = useNavigation();
|
||||
const { register: legacyRegisterFromContext } = useLegacyAuthContext();
|
||||
const { register: registerFromContext } = useAuthBindingsContext();
|
||||
const { close, open } = useNotification();
|
||||
|
||||
const { keys, preferLegacyKeys } = useKeys();
|
||||
|
||||
const mutation = useMutation<
|
||||
AuthActionResponse,
|
||||
Error | RefineError,
|
||||
TVariables,
|
||||
unknown
|
||||
>({
|
||||
mutationKey: keys().auth().action("register").get(preferLegacyKeys),
|
||||
mutationFn: registerFromContext,
|
||||
onSuccess: async ({ success, redirectTo, error, successNotification }) => {
|
||||
if (success) {
|
||||
close?.("register-error");
|
||||
|
||||
if (successNotification) {
|
||||
open?.(buildSuccessNotification(successNotification));
|
||||
}
|
||||
}
|
||||
|
||||
if (error || !success) {
|
||||
open?.(buildNotification(error));
|
||||
}
|
||||
|
||||
if (redirectTo) {
|
||||
if (routerType === "legacy") {
|
||||
replace(redirectTo);
|
||||
} else {
|
||||
go({ to: redirectTo, type: "replace" });
|
||||
}
|
||||
} else {
|
||||
if (routerType === "legacy") {
|
||||
replace("/");
|
||||
}
|
||||
}
|
||||
|
||||
await invalidateAuthStore();
|
||||
},
|
||||
onError: (error: any) => {
|
||||
open?.(buildNotification(error));
|
||||
},
|
||||
...(v3LegacyAuthProviderCompatible === true ? {} : mutationOptions),
|
||||
meta: {
|
||||
...(v3LegacyAuthProviderCompatible === true ? {} : mutationOptions?.meta),
|
||||
...getXRay("useRegister", preferLegacyKeys),
|
||||
},
|
||||
});
|
||||
|
||||
const v3LegacyAuthProviderCompatibleMutation = useMutation<
|
||||
TRegisterData,
|
||||
Error | RefineError,
|
||||
TVariables,
|
||||
unknown
|
||||
>({
|
||||
mutationKey: [
|
||||
...keys().auth().action("register").get(preferLegacyKeys),
|
||||
"v3LegacyAuthProviderCompatible",
|
||||
],
|
||||
mutationFn: legacyRegisterFromContext,
|
||||
onSuccess: async (redirectPathFromAuth) => {
|
||||
if (redirectPathFromAuth !== false) {
|
||||
if (redirectPathFromAuth) {
|
||||
if (routerType === "legacy") {
|
||||
replace(redirectPathFromAuth);
|
||||
} else {
|
||||
go({ to: redirectPathFromAuth, type: "replace" });
|
||||
}
|
||||
} else {
|
||||
if (routerType === "legacy") {
|
||||
replace("/");
|
||||
} else {
|
||||
go({ to: "/", type: "replace" });
|
||||
}
|
||||
}
|
||||
|
||||
await invalidateAuthStore();
|
||||
|
||||
close?.("register-error");
|
||||
}
|
||||
},
|
||||
onError: (error: any) => {
|
||||
open?.(buildNotification(error));
|
||||
},
|
||||
...(v3LegacyAuthProviderCompatible ? mutationOptions : {}),
|
||||
meta: {
|
||||
...(v3LegacyAuthProviderCompatible ? mutationOptions?.meta : {}),
|
||||
...getXRay("useRegister", preferLegacyKeys),
|
||||
},
|
||||
});
|
||||
|
||||
return v3LegacyAuthProviderCompatible
|
||||
? v3LegacyAuthProviderCompatibleMutation
|
||||
: mutation;
|
||||
}
|
||||
|
||||
const buildNotification = (
|
||||
error?: Error | RefineError,
|
||||
): OpenNotificationParams => {
|
||||
return {
|
||||
message: error?.name || "Register Error",
|
||||
description: error?.message || "Error while registering",
|
||||
key: "register-error",
|
||||
type: "error",
|
||||
};
|
||||
};
|
||||
|
||||
const buildSuccessNotification = (
|
||||
successNotification: SuccessNotificationResponse,
|
||||
): OpenNotificationParams => {
|
||||
return {
|
||||
message: successNotification.message,
|
||||
description: successNotification.description,
|
||||
key: "register-success",
|
||||
type: "success",
|
||||
};
|
||||
};
|
||||
857
packages/core/src/hooks/auth/useUpdatePassword/index.spec.ts
Normal file
857
packages/core/src/hooks/auth/useUpdatePassword/index.spec.ts
Normal file
@@ -0,0 +1,857 @@
|
||||
import { renderHook, waitFor } from "@testing-library/react";
|
||||
|
||||
import {
|
||||
TestWrapper,
|
||||
act,
|
||||
mockLegacyAuthProvider,
|
||||
mockLegacyRouterProvider,
|
||||
mockRouterProvider,
|
||||
queryClient,
|
||||
} from "@test";
|
||||
|
||||
import type { LegacyRouterProvider } from "../../../contexts/router/legacy/types";
|
||||
import { useUpdatePassword } from "./";
|
||||
|
||||
const mockFn = jest.fn();
|
||||
|
||||
const legacyRouterProvider: LegacyRouterProvider = {
|
||||
...mockLegacyRouterProvider(),
|
||||
useHistory: () => ({
|
||||
goBack: jest.fn(),
|
||||
push: jest.fn(),
|
||||
replace: mockFn,
|
||||
}),
|
||||
};
|
||||
|
||||
const mockAuthProvider = {
|
||||
login: () => Promise.resolve({ success: true }),
|
||||
check: () => Promise.resolve({ authenticated: true }),
|
||||
onError: () => Promise.resolve({}),
|
||||
logout: () => Promise.resolve({ success: true }),
|
||||
};
|
||||
|
||||
// NOTE : Will be removed in v5
|
||||
describe("v3LegacyAuthProviderCompatible useUpdatePassword Hook", () => {
|
||||
beforeEach(() => {
|
||||
mockFn.mockReset();
|
||||
jest.spyOn(console, "error").mockImplementation((message) => {
|
||||
if (message?.message === "Missing fields") return;
|
||||
if (typeof message === "undefined") return;
|
||||
console.warn(message);
|
||||
});
|
||||
});
|
||||
|
||||
it("succeed update password", async () => {
|
||||
const { result } = renderHook(
|
||||
() => useUpdatePassword({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
updatePassword: ({ password, confirmPassword }) => {
|
||||
if (password && confirmPassword) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(new Error("Missing fields"));
|
||||
},
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
getPermissions: () => Promise.resolve(),
|
||||
logout: () => Promise.resolve(),
|
||||
getUserIdentity: () => Promise.resolve({ id: 1 }),
|
||||
},
|
||||
legacyRouterProvider,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const { mutate: updatePassword } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
updatePassword({ password: "123", confirmPassword: "321" });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(mockFn).not.toBeCalledWith();
|
||||
});
|
||||
|
||||
it("fail update password", async () => {
|
||||
const { result } = renderHook(
|
||||
() => useUpdatePassword({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
updatePassword: () => Promise.reject(new Error("Missing fields")),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
getPermissions: () => Promise.resolve(),
|
||||
logout: () => Promise.resolve(),
|
||||
getUserIdentity: () => Promise.resolve({ id: 1 }),
|
||||
},
|
||||
legacyRouterProvider,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const { mutate: updatePassword } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
updatePassword({ password: "123" });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isError).toBeTruthy();
|
||||
});
|
||||
|
||||
const { error } = result.current ?? { error: undefined };
|
||||
|
||||
expect(error).toEqual(new Error("Missing fields"));
|
||||
});
|
||||
|
||||
it("should open notification when has success is false, error is undefined", async () => {
|
||||
const updatePasswordMock = jest.fn();
|
||||
const openNotificationMock = jest.fn();
|
||||
const closeNotificationMock = jest.fn();
|
||||
|
||||
const { result } = renderHook(
|
||||
() => useUpdatePassword({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
notificationProvider: {
|
||||
open: openNotificationMock,
|
||||
close: closeNotificationMock,
|
||||
},
|
||||
legacyAuthProvider: {
|
||||
...mockLegacyAuthProvider,
|
||||
updatePassword: updatePasswordMock,
|
||||
},
|
||||
routerProvider: legacyRouterProvider,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const { mutate: updatePassword } = result.current;
|
||||
|
||||
updatePasswordMock.mockRejectedValueOnce({});
|
||||
await act(async () => {
|
||||
updatePassword({});
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(openNotificationMock).toBeCalledWith({
|
||||
key: "update-password-error",
|
||||
type: "error",
|
||||
message: "Update Password Error",
|
||||
description: "Error while updating password",
|
||||
});
|
||||
});
|
||||
|
||||
updatePasswordMock.mockResolvedValueOnce(false);
|
||||
await act(async () => {
|
||||
updatePassword({});
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(closeNotificationMock).toBeCalledWith("update-password-error");
|
||||
});
|
||||
});
|
||||
|
||||
it("should open notification when throw error", async () => {
|
||||
const openNotificationMock = jest.fn();
|
||||
|
||||
const { result } = renderHook(
|
||||
() => useUpdatePassword({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
notificationProvider: {
|
||||
open: openNotificationMock,
|
||||
close: jest.fn(),
|
||||
},
|
||||
routerProvider: legacyRouterProvider,
|
||||
legacyAuthProvider: {
|
||||
...mockLegacyAuthProvider,
|
||||
updatePassword: () => {
|
||||
throw new Error("Unhandled error");
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const { mutate: updatePassword } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
updatePassword({});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(openNotificationMock).toBeCalledWith({
|
||||
key: "update-password-error",
|
||||
type: "error",
|
||||
message: "Error",
|
||||
description: "Unhandled error",
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("useUpdatePassword Hook", () => {
|
||||
beforeEach(() => {
|
||||
mockFn.mockReset();
|
||||
jest.spyOn(console, "error").mockImplementation((message) => {
|
||||
if (message?.message === "Missing fields") return;
|
||||
if (typeof message === "undefined") return;
|
||||
console.warn(message);
|
||||
});
|
||||
});
|
||||
|
||||
it("succeed update password with legacyRouterProvider", async () => {
|
||||
const { result } = renderHook(() => useUpdatePassword(), {
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
updatePassword: ({ password, confirmPassword }: any) => {
|
||||
if (password && confirmPassword) {
|
||||
return Promise.resolve({ success: true });
|
||||
}
|
||||
return Promise.resolve({
|
||||
success: false,
|
||||
error: new Error("Missing fields"),
|
||||
});
|
||||
},
|
||||
},
|
||||
legacyRouterProvider,
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: updatePassword } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
updatePassword({ password: "123", confirmPassword: "321" });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(result.current.data).toEqual({ success: true });
|
||||
expect(mockFn).not.toBeCalledWith();
|
||||
});
|
||||
|
||||
it("succeed update password", async () => {
|
||||
const mockGo = jest.fn();
|
||||
|
||||
const { result } = renderHook(() => useUpdatePassword(), {
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
updatePassword: ({ password, confirmPassword }: any) => {
|
||||
if (password && confirmPassword) {
|
||||
return Promise.resolve({
|
||||
success: true,
|
||||
redirectTo: "redirectTo",
|
||||
});
|
||||
}
|
||||
return Promise.resolve({
|
||||
success: false,
|
||||
error: new Error("Missing fields"),
|
||||
});
|
||||
},
|
||||
},
|
||||
routerProvider: mockRouterProvider({
|
||||
fns: {
|
||||
go: () => mockGo,
|
||||
},
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: updatePassword } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
updatePassword({ password: "123", confirmPassword: "321" });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(mockGo).toBeCalledWith({
|
||||
to: "redirectTo",
|
||||
type: "replace",
|
||||
});
|
||||
});
|
||||
|
||||
it("fail update password", async () => {
|
||||
const { result } = renderHook(() => useUpdatePassword(), {
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
updatePassword: ({ password, confirmPassword }: any) => {
|
||||
if (password && confirmPassword) {
|
||||
return Promise.resolve({ success: true });
|
||||
}
|
||||
return Promise.resolve({
|
||||
success: false,
|
||||
error: new Error("Missing fields"),
|
||||
});
|
||||
},
|
||||
},
|
||||
legacyRouterProvider,
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: updatePassword } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
updatePassword({ password: "123" });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(result.current.data).toEqual({
|
||||
success: false,
|
||||
error: new Error("Missing fields"),
|
||||
});
|
||||
expect(mockFn).not.toBeCalledWith();
|
||||
});
|
||||
|
||||
it("success and redirect", async () => {
|
||||
const { result } = renderHook(() => useUpdatePassword(), {
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
|
||||
updatePassword: ({ password, confirmPassword }: any) => {
|
||||
if (password && confirmPassword) {
|
||||
return Promise.resolve({
|
||||
success: true,
|
||||
redirectTo: "/login",
|
||||
});
|
||||
}
|
||||
return Promise.resolve({
|
||||
success: false,
|
||||
error: new Error("Missing fields"),
|
||||
});
|
||||
},
|
||||
},
|
||||
legacyRouterProvider,
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: updatePassword } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
updatePassword({ password: "123", confirmPassword: "321" });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(result.current.data).toEqual({
|
||||
success: true,
|
||||
redirectTo: "/login",
|
||||
});
|
||||
expect(mockFn).toBeCalledWith("/login");
|
||||
});
|
||||
|
||||
it("fail and redirect", async () => {
|
||||
mockFn;
|
||||
const { result } = renderHook(() => useUpdatePassword(), {
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
|
||||
updatePassword: ({ password, confirmPassword }: any) => {
|
||||
if (password && confirmPassword) {
|
||||
return Promise.resolve({
|
||||
success: true,
|
||||
});
|
||||
}
|
||||
return Promise.resolve({
|
||||
success: false,
|
||||
error: new Error("Missing fields"),
|
||||
redirectTo: "/register",
|
||||
});
|
||||
},
|
||||
},
|
||||
legacyRouterProvider,
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: updatePassword } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
updatePassword({ confirmPassword: "321" });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(result.current.data).toEqual({
|
||||
success: false,
|
||||
error: new Error("Missing fields"),
|
||||
redirectTo: "/register",
|
||||
});
|
||||
expect(mockFn).toBeCalledWith("/register");
|
||||
});
|
||||
|
||||
it("should open notification when has error is true", async () => {
|
||||
const openNotificationMock = jest.fn();
|
||||
|
||||
const { result } = renderHook(() => useUpdatePassword(), {
|
||||
wrapper: TestWrapper({
|
||||
notificationProvider: {
|
||||
open: openNotificationMock,
|
||||
close: jest.fn(),
|
||||
},
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
updatePassword: () =>
|
||||
Promise.resolve({
|
||||
success: false,
|
||||
error: new Error("Error"),
|
||||
}),
|
||||
},
|
||||
legacyRouterProvider,
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: updatePassword } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
updatePassword({});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(openNotificationMock).toBeCalledWith({
|
||||
key: "update-password-error",
|
||||
type: "error",
|
||||
message: "Error",
|
||||
description: "Error",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should open notification when has success is false, error is undefined", async () => {
|
||||
const updatePasswordMock = jest.fn();
|
||||
const openNotificationMock = jest.fn();
|
||||
const closeNotificationMock = jest.fn();
|
||||
|
||||
const { result } = renderHook(() => useUpdatePassword(), {
|
||||
wrapper: TestWrapper({
|
||||
notificationProvider: {
|
||||
open: openNotificationMock,
|
||||
close: closeNotificationMock,
|
||||
},
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
updatePassword: updatePasswordMock,
|
||||
},
|
||||
routerProvider: legacyRouterProvider,
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: updatePassword } = result.current;
|
||||
|
||||
updatePasswordMock.mockResolvedValueOnce({
|
||||
success: false,
|
||||
});
|
||||
await act(async () => {
|
||||
updatePassword({});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(openNotificationMock).toBeCalledWith({
|
||||
key: "update-password-error",
|
||||
type: "error",
|
||||
message: "Update Password Error",
|
||||
description: "Error while updating password",
|
||||
});
|
||||
});
|
||||
|
||||
updatePasswordMock.mockResolvedValueOnce({
|
||||
success: true,
|
||||
});
|
||||
await act(async () => {
|
||||
updatePassword({});
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(closeNotificationMock).toBeCalledWith("update-password-error");
|
||||
});
|
||||
});
|
||||
|
||||
it("should open notification when throw error", async () => {
|
||||
const openNotificationMock = jest.fn();
|
||||
|
||||
const { result } = renderHook(() => useUpdatePassword(), {
|
||||
wrapper: TestWrapper({
|
||||
notificationProvider: {
|
||||
open: openNotificationMock,
|
||||
close: jest.fn(),
|
||||
},
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
updatePassword: () => {
|
||||
throw new Error("Unhandled error");
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: updatePassword } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
updatePassword({});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(openNotificationMock).toBeCalledWith({
|
||||
key: "update-password-error",
|
||||
type: "error",
|
||||
message: "Error",
|
||||
description: "Unhandled error",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should work notificationProvider", async () => {
|
||||
const onErrorMock = jest.fn();
|
||||
const onSuccessMock = jest.fn();
|
||||
const updatePasswordMock = jest.fn();
|
||||
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useUpdatePassword({
|
||||
mutationOptions: {
|
||||
onSuccess: onSuccessMock,
|
||||
onError: onErrorMock,
|
||||
},
|
||||
}),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
updatePassword: updatePasswordMock,
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const { mutate: updatePassword } = result.current;
|
||||
|
||||
updatePasswordMock.mockRejectedValueOnce(new Error("Error"));
|
||||
await act(async () => {
|
||||
updatePassword({});
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(onErrorMock).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
updatePasswordMock.mockResolvedValueOnce({
|
||||
success: false,
|
||||
});
|
||||
await act(async () => {
|
||||
updatePassword({});
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(onSuccessMock).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
updatePasswordMock.mockResolvedValueOnce({
|
||||
success: true,
|
||||
});
|
||||
await act(async () => {
|
||||
updatePassword({});
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(onSuccessMock).toBeCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
it("should override `mutationFn` with mutationOptions.mutationFn", async () => {
|
||||
const updatePasswordMock = jest.fn().mockResolvedValue({ data: {} });
|
||||
const mutationFnMock = jest.fn().mockResolvedValue({ data: {} });
|
||||
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useUpdatePassword({
|
||||
mutationOptions: {
|
||||
// mutationFn is omitted in types. So we need to use @ts-ignore test it.
|
||||
// @ts-ignore
|
||||
mutationFn: mutationFnMock,
|
||||
},
|
||||
}),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
updatePassword: updatePasswordMock,
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
result.current.mutate({});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(updatePasswordMock).not.toBeCalled();
|
||||
expect(mutationFnMock).toBeCalled();
|
||||
});
|
||||
|
||||
it("should override `mutationKey` with `mutationOptions.mutationKey`", async () => {
|
||||
const updatePasswordMock = jest.fn().mockResolvedValue({ data: {} });
|
||||
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useUpdatePassword({
|
||||
mutationOptions: {
|
||||
mutationKey: ["foo", "bar"],
|
||||
},
|
||||
}),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
updatePassword: updatePasswordMock,
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
result.current.mutate({});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(
|
||||
queryClient.getMutationCache().findAll({
|
||||
mutationKey: ["foo", "bar"],
|
||||
}),
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("should open success notification when successNotification is passed", async () => {
|
||||
const openNotificationMock = jest.fn();
|
||||
|
||||
const successNotification = {
|
||||
message: "Success!",
|
||||
description: "Operation completed successfully",
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useUpdatePassword(), {
|
||||
wrapper: TestWrapper({
|
||||
notificationProvider: {
|
||||
open: openNotificationMock,
|
||||
close: jest.fn(),
|
||||
},
|
||||
authProvider: {
|
||||
...mockAuthProvider,
|
||||
updatePassword: () =>
|
||||
Promise.resolve({
|
||||
success: true,
|
||||
successNotification,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
result.current.mutate({});
|
||||
});
|
||||
|
||||
expect(openNotificationMock).toHaveBeenCalledWith({
|
||||
key: "update-password-success",
|
||||
type: "success",
|
||||
message: "Success!",
|
||||
description: "Operation completed successfully",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// NOTE : Will be removed in v5
|
||||
describe("useUpdatePassword Hook authProvider selection", () => {
|
||||
it("selects new authProvider", async () => {
|
||||
const legacyUpdatePassword = jest.fn(() => Promise.resolve());
|
||||
const updatePassword = jest.fn(() => Promise.resolve({ success: true }));
|
||||
|
||||
const { result } = renderHook(() => useUpdatePassword(), {
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
logout: () => Promise.resolve(),
|
||||
updatePassword: () => legacyUpdatePassword(),
|
||||
},
|
||||
authProvider: {
|
||||
login: () => Promise.resolve({ success: true }),
|
||||
check: () => Promise.resolve({ authenticated: true }),
|
||||
onError: () => Promise.resolve({}),
|
||||
logout: () => Promise.resolve({ success: true }),
|
||||
updatePassword: () => updatePassword(),
|
||||
},
|
||||
legacyRouterProvider,
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutate: login } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
login({});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBeFalsy();
|
||||
});
|
||||
|
||||
expect(legacyUpdatePassword).not.toHaveBeenCalled();
|
||||
expect(updatePassword).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("selects v3LegacyAuthProviderCompatible authProvider", async () => {
|
||||
const legacyUpdatePassword = jest.fn(() => Promise.resolve());
|
||||
const updatePassword = jest.fn(() => Promise.resolve({ success: true }));
|
||||
|
||||
const { result } = renderHook(
|
||||
() => useUpdatePassword({ v3LegacyAuthProviderCompatible: true }),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
logout: () => Promise.resolve(),
|
||||
updatePassword: () => legacyUpdatePassword(),
|
||||
},
|
||||
authProvider: {
|
||||
login: () => Promise.resolve({ success: true }),
|
||||
check: () => Promise.resolve({ authenticated: true }),
|
||||
onError: () => Promise.resolve({}),
|
||||
logout: () => Promise.resolve({ success: true }),
|
||||
updatePassword: () => updatePassword(),
|
||||
},
|
||||
legacyRouterProvider,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const { mutate: login } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
login({});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBeFalsy();
|
||||
});
|
||||
|
||||
expect(legacyUpdatePassword).toHaveBeenCalled();
|
||||
expect(updatePassword).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("useUpdatePassword Hook with v3LegacyAuthProviderCompatible", () => {
|
||||
it("should be redirect legacyRouterProvider", async () => {
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useUpdatePassword({
|
||||
v3LegacyAuthProviderCompatible: true,
|
||||
}),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
updatePassword: ({ password, confirmPassword }) => {
|
||||
if (password && confirmPassword) {
|
||||
return Promise.resolve("redirectTo");
|
||||
}
|
||||
return Promise.reject(new Error("Missing fields"));
|
||||
},
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
getPermissions: () => Promise.resolve(),
|
||||
logout: () => Promise.resolve(),
|
||||
getUserIdentity: () => Promise.resolve({ id: 1 }),
|
||||
},
|
||||
legacyRouterProvider: {
|
||||
...mockLegacyRouterProvider(),
|
||||
useHistory: () => ({
|
||||
goBack: jest.fn(),
|
||||
replace: mockFn,
|
||||
push: jest.fn(),
|
||||
}),
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const { mutate: updatePassword } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
updatePassword({ password: "123", confirmPassword: "321" });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(mockFn).toBeCalledWith("redirectTo");
|
||||
});
|
||||
|
||||
it("should be redirect routerProvider", async () => {
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useUpdatePassword({
|
||||
v3LegacyAuthProviderCompatible: true,
|
||||
}),
|
||||
{
|
||||
wrapper: TestWrapper({
|
||||
legacyAuthProvider: {
|
||||
login: () => Promise.resolve(),
|
||||
updatePassword: ({ password, confirmPassword }) => {
|
||||
if (password && confirmPassword) {
|
||||
return Promise.resolve("redirectTo");
|
||||
}
|
||||
return Promise.reject(new Error("Missing fields"));
|
||||
},
|
||||
checkAuth: () => Promise.resolve(),
|
||||
checkError: () => Promise.resolve(),
|
||||
getPermissions: () => Promise.resolve(),
|
||||
logout: () => Promise.resolve(),
|
||||
getUserIdentity: () => Promise.resolve({ id: 1 }),
|
||||
},
|
||||
routerProvider: {
|
||||
...mockRouterProvider({
|
||||
fns: {
|
||||
go: () => mockFn,
|
||||
},
|
||||
}),
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const { mutate: updatePassword } = result.current;
|
||||
|
||||
await act(async () => {
|
||||
updatePassword({ password: "123", confirmPassword: "321" });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(mockFn).toBeCalledWith("redirectTo");
|
||||
});
|
||||
});
|
||||
261
packages/core/src/hooks/auth/useUpdatePassword/index.ts
Normal file
261
packages/core/src/hooks/auth/useUpdatePassword/index.ts
Normal file
@@ -0,0 +1,261 @@
|
||||
import React from "react";
|
||||
|
||||
import { getXRay } from "@refinedev/devtools-internal";
|
||||
import {
|
||||
type UseMutationOptions,
|
||||
type UseMutationResult,
|
||||
useMutation,
|
||||
} from "@tanstack/react-query";
|
||||
import qs from "qs";
|
||||
|
||||
import { useAuthBindingsContext, useLegacyAuthContext } from "@contexts/auth";
|
||||
import {
|
||||
useGo,
|
||||
useKeys,
|
||||
useNavigation,
|
||||
useNotification,
|
||||
useParsed,
|
||||
useRouterContext,
|
||||
useRouterType,
|
||||
} from "@hooks";
|
||||
|
||||
import type { UpdatePasswordFormTypes } from "../../../components/pages/auth/types";
|
||||
import type {
|
||||
AuthActionResponse,
|
||||
SuccessNotificationResponse,
|
||||
TUpdatePasswordData,
|
||||
} from "../../../contexts/auth/types";
|
||||
import type { RefineError } from "../../../contexts/data/types";
|
||||
import type { OpenNotificationParams } from "../../../contexts/notification/types";
|
||||
|
||||
export type UseUpdatePasswordLegacyProps<
|
||||
TVariables extends UpdatePasswordFormTypes,
|
||||
> = {
|
||||
v3LegacyAuthProviderCompatible: true;
|
||||
mutationOptions?: Omit<
|
||||
UseMutationOptions<
|
||||
TUpdatePasswordData,
|
||||
Error | RefineError,
|
||||
TVariables,
|
||||
unknown
|
||||
>,
|
||||
"mutationFn" | "onError" | "onSuccess"
|
||||
>;
|
||||
};
|
||||
|
||||
export type UseUpdatePasswordProps<TVariables extends UpdatePasswordFormTypes> =
|
||||
{
|
||||
v3LegacyAuthProviderCompatible?: false;
|
||||
mutationOptions?: Omit<
|
||||
UseMutationOptions<
|
||||
AuthActionResponse,
|
||||
Error | RefineError,
|
||||
TVariables,
|
||||
unknown
|
||||
>,
|
||||
"mutationFn"
|
||||
>;
|
||||
};
|
||||
|
||||
export type UseUpdatePasswordCombinedProps<
|
||||
TVariables extends UpdatePasswordFormTypes,
|
||||
> = {
|
||||
v3LegacyAuthProviderCompatible: boolean;
|
||||
mutationOptions?: Omit<
|
||||
UseMutationOptions<
|
||||
AuthActionResponse | TUpdatePasswordData,
|
||||
Error | RefineError,
|
||||
TVariables,
|
||||
unknown
|
||||
>,
|
||||
"mutationFn"
|
||||
>;
|
||||
};
|
||||
|
||||
export type UseUpdatePasswordLegacyReturnType<
|
||||
TVariables extends UpdatePasswordFormTypes,
|
||||
> = UseMutationResult<
|
||||
TUpdatePasswordData,
|
||||
Error | RefineError,
|
||||
TVariables,
|
||||
unknown
|
||||
>;
|
||||
|
||||
export type UseUpdatePasswordReturnType<
|
||||
TVariables extends UpdatePasswordFormTypes,
|
||||
> = UseMutationResult<
|
||||
AuthActionResponse,
|
||||
Error | RefineError,
|
||||
TVariables,
|
||||
unknown
|
||||
>;
|
||||
|
||||
export type UseUpdatePasswordCombinedReturnType<
|
||||
TVariables extends UpdatePasswordFormTypes,
|
||||
> = UseMutationResult<
|
||||
AuthActionResponse | TUpdatePasswordData,
|
||||
Error | RefineError,
|
||||
TVariables,
|
||||
unknown
|
||||
>;
|
||||
|
||||
export function useUpdatePassword<TVariables extends UpdatePasswordFormTypes>(
|
||||
props: UseUpdatePasswordLegacyProps<TVariables>,
|
||||
): UseUpdatePasswordLegacyReturnType<TVariables>;
|
||||
|
||||
export function useUpdatePassword<TVariables extends UpdatePasswordFormTypes>(
|
||||
props?: UseUpdatePasswordProps<TVariables>,
|
||||
): UseUpdatePasswordReturnType<TVariables>;
|
||||
|
||||
export function useUpdatePassword<TVariables extends UpdatePasswordFormTypes>(
|
||||
props?: UseUpdatePasswordCombinedProps<TVariables>,
|
||||
): UseUpdatePasswordCombinedReturnType<TVariables>;
|
||||
|
||||
/**
|
||||
* `useUpdatePassword` calls `updatePassword` method from {@link https://refine.dev/docs/api-reference/core/providers/auth-provider `authProvider`} under the hood.
|
||||
*
|
||||
* @see {@link https://refine.dev/docs/api-reference/core/hooks/auth/useUpdatePassword} for more details.
|
||||
*
|
||||
* @typeParam TData - Result data of the query
|
||||
* @typeParam TVariables - Values for mutation function. default `{}`
|
||||
*
|
||||
*/
|
||||
export function useUpdatePassword<
|
||||
TVariables extends UpdatePasswordFormTypes = {},
|
||||
>({
|
||||
v3LegacyAuthProviderCompatible,
|
||||
mutationOptions,
|
||||
}:
|
||||
| UseUpdatePasswordProps<TVariables>
|
||||
| UseUpdatePasswordLegacyProps<TVariables> = {}):
|
||||
| UseUpdatePasswordReturnType<TVariables>
|
||||
| UseUpdatePasswordLegacyReturnType<TVariables> {
|
||||
const routerType = useRouterType();
|
||||
|
||||
const go = useGo();
|
||||
const { replace } = useNavigation();
|
||||
const { updatePassword: legacyUpdatePasswordFromContext } =
|
||||
useLegacyAuthContext();
|
||||
const { updatePassword: updatePasswordFromContext } =
|
||||
useAuthBindingsContext();
|
||||
const { close, open } = useNotification();
|
||||
|
||||
const { keys, preferLegacyKeys } = useKeys();
|
||||
|
||||
const parsed = useParsed();
|
||||
const { useLocation } = useRouterContext();
|
||||
const { search } = useLocation();
|
||||
|
||||
const params = React.useMemo(() => {
|
||||
if (routerType === "legacy") {
|
||||
const queryStrings = qs.parse(search, {
|
||||
ignoreQueryPrefix: true,
|
||||
});
|
||||
return queryStrings ?? {};
|
||||
}
|
||||
return parsed.params ?? {};
|
||||
}, [search, parsed, routerType]);
|
||||
|
||||
const mutation = useMutation<AuthActionResponse, Error, TVariables, unknown>({
|
||||
mutationKey: keys().auth().action("updatePassword").get(preferLegacyKeys),
|
||||
mutationFn: async (variables) => {
|
||||
return updatePasswordFromContext?.({
|
||||
...params,
|
||||
...variables,
|
||||
}) as Promise<AuthActionResponse>;
|
||||
},
|
||||
onSuccess: ({ success, redirectTo, error, successNotification }) => {
|
||||
if (success) {
|
||||
close?.("update-password-error");
|
||||
|
||||
if (successNotification) {
|
||||
open?.(buildSuccessNotification(successNotification));
|
||||
}
|
||||
}
|
||||
|
||||
if (error || !success) {
|
||||
open?.(buildNotification(error));
|
||||
}
|
||||
|
||||
if (redirectTo) {
|
||||
if (routerType === "legacy") {
|
||||
replace(redirectTo);
|
||||
} else {
|
||||
go({ to: redirectTo, type: "replace" });
|
||||
}
|
||||
}
|
||||
},
|
||||
onError: (error: any) => {
|
||||
open?.(buildNotification(error));
|
||||
},
|
||||
...(v3LegacyAuthProviderCompatible === true ? {} : mutationOptions),
|
||||
meta: {
|
||||
...(v3LegacyAuthProviderCompatible === true ? {} : mutationOptions?.meta),
|
||||
...getXRay("useUpdatePassword", preferLegacyKeys),
|
||||
},
|
||||
});
|
||||
|
||||
const v3LegacyAuthProviderCompatibleMutation = useMutation<
|
||||
TUpdatePasswordData,
|
||||
Error | RefineError,
|
||||
TVariables,
|
||||
unknown
|
||||
>({
|
||||
mutationKey: [
|
||||
...keys().auth().action("updatePassword").get(preferLegacyKeys),
|
||||
"v3LegacyAuthProviderCompatible",
|
||||
],
|
||||
mutationFn: async (variables) => {
|
||||
return legacyUpdatePasswordFromContext?.({
|
||||
...params,
|
||||
...variables,
|
||||
});
|
||||
},
|
||||
onSuccess: (redirectPathFromAuth) => {
|
||||
if (redirectPathFromAuth !== false) {
|
||||
if (redirectPathFromAuth) {
|
||||
if (routerType === "legacy") {
|
||||
replace(redirectPathFromAuth);
|
||||
} else {
|
||||
go({ to: redirectPathFromAuth, type: "replace" });
|
||||
}
|
||||
}
|
||||
}
|
||||
close?.("update-password-error");
|
||||
},
|
||||
onError: (error: any) => {
|
||||
open?.(buildNotification(error));
|
||||
},
|
||||
...(v3LegacyAuthProviderCompatible ? mutationOptions : {}),
|
||||
meta: {
|
||||
...(v3LegacyAuthProviderCompatible ? mutationOptions?.meta : {}),
|
||||
...getXRay("useUpdatePassword", preferLegacyKeys),
|
||||
},
|
||||
});
|
||||
|
||||
return v3LegacyAuthProviderCompatible
|
||||
? v3LegacyAuthProviderCompatibleMutation
|
||||
: mutation;
|
||||
}
|
||||
|
||||
const buildNotification = (
|
||||
error?: Error | RefineError,
|
||||
): OpenNotificationParams => {
|
||||
return {
|
||||
message: error?.name || "Update Password Error",
|
||||
description: error?.message || "Error while updating password",
|
||||
key: "update-password-error",
|
||||
type: "error",
|
||||
};
|
||||
};
|
||||
|
||||
const buildSuccessNotification = (
|
||||
successNotification: SuccessNotificationResponse,
|
||||
): OpenNotificationParams => {
|
||||
return {
|
||||
message: successNotification.message,
|
||||
description: successNotification.description,
|
||||
key: "update-password-success",
|
||||
type: "success",
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user