From 655955006de550d8e396ee2c22bf68cad65c2fd2 Mon Sep 17 00:00:00 2001 From: amalv <1252707+amalv@users.noreply.github.com> Date: Sun, 7 Jan 2024 01:03:02 +0100 Subject: [PATCH] feat: extract AuthProvider from AuthContext and add tests --- package.json | 3 +- src/App.test.tsx | 2 +- src/contexts/AuthContext.tsx | 58 ++---------------------------- src/contexts/AuthProvider.test.tsx | 29 +++++++++++++++ src/contexts/AuthProvider.tsx | 56 +++++++++++++++++++++++++++++ src/contexts/index.ts | 1 + vitest.config.ts | 2 ++ 7 files changed, 93 insertions(+), 58 deletions(-) create mode 100644 src/contexts/AuthProvider.test.tsx create mode 100644 src/contexts/AuthProvider.tsx diff --git a/package.json b/package.json index 4656926..1000710 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "preview": "vite preview", "semantic-release": "semantic-release", "test": "vitest", + "test:staged": "CI=true npm test", "prepare": "husky install" }, "dependencies": { @@ -57,6 +58,6 @@ }, "lint-staged": { "*.{ts,tsx}": "eslint --fix", - "*test.{ts,tsx}": "npm run test" + "*test.{ts,tsx}": "npm run test:staged" } } diff --git a/src/App.test.tsx b/src/App.test.tsx index 7cade84..0690a8d 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -9,7 +9,7 @@ vi.mock("@auth0/auth0-react", () => ({ Auth0Provider: ({ children }: { children: ReactNode }) => children, })); -vi.mock("./contexts/AuthContext", () => ({ +vi.mock("./contexts/AuthProvider", () => ({ AuthProvider: ({ children }: { children: ReactNode }) => children, })); diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx index a85bf3b..8ab3764 100644 --- a/src/contexts/AuthContext.tsx +++ b/src/contexts/AuthContext.tsx @@ -1,66 +1,12 @@ -import { createContext, useState, useEffect, useMemo } from "react"; -import { IdToken, useAuth0, User } from "@auth0/auth0-react"; -import { jwtDecode } from "jwt-decode"; - -interface DecodedToken { - exp: number; - iat: number; - iss: string; - sub: string; - aud: string; -} +import { createContext } from "react"; +import { User } from "@auth0/auth0-react"; interface AuthContextProps { token: string | null; user: User | undefined; } -interface AuthProviderProps { - children: React.ReactNode; -} - export const AuthContext = createContext({ token: null, user: undefined, }); - -const refreshToken = ( - getIdTokenClaims: () => Promise, - setToken: (token: string | null) => void -) => { - getIdTokenClaims().then((claims) => { - if (claims) { - const idToken = claims.__raw; // The raw id_token - localStorage.setItem("auth0.token", idToken); - setToken(idToken); - - try { - const decodedToken: DecodedToken = jwtDecode(idToken); - const expiryTime = decodedToken.exp * 1000; // Convert to milliseconds - const timeoutDuration = expiryTime - Date.now() - 60 * 1000; // Refresh 1 minute before expiry - - setTimeout( - () => refreshToken(getIdTokenClaims, setToken), - timeoutDuration - ); - } catch (error) { - console.error("Invalid token", error); - } - } - }); -}; - -export const AuthProvider: React.FC = ({ children }) => { - const { user, getIdTokenClaims } = useAuth0(); - const [token, setToken] = useState(null); - - useEffect(() => { - if (user && getIdTokenClaims) { - refreshToken(getIdTokenClaims, setToken); - } - }, [user, getIdTokenClaims]); - - const value = useMemo(() => ({ token, user }), [token, user]); - - return {children}; -}; diff --git a/src/contexts/AuthProvider.test.tsx b/src/contexts/AuthProvider.test.tsx new file mode 100644 index 0000000..34003b4 --- /dev/null +++ b/src/contexts/AuthProvider.test.tsx @@ -0,0 +1,29 @@ +import { render, act, screen } from "@testing-library/react"; +import { AuthProvider } from "./AuthProvider"; +import { useAuth0 } from "@auth0/auth0-react"; +import { vi } from "vitest"; + +vi.mock("@auth0/auth0-react", () => ({ + useAuth0: vi.fn().mockReturnValue({ + user: { name: "Test User" }, + getIdTokenClaims: vi.fn().mockResolvedValue({ + __raw: + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c", + }), + }), +})); + +describe("AuthProvider", () => { + it("provides auth context", async () => { + await act(async () => { + render( + +
Test
+
+ ); + }); + + expect(screen.getByText("Test")).toBeInTheDocument(); + expect(useAuth0().getIdTokenClaims).toHaveBeenCalled(); + }); +}); diff --git a/src/contexts/AuthProvider.tsx b/src/contexts/AuthProvider.tsx new file mode 100644 index 0000000..3ca3fb8 --- /dev/null +++ b/src/contexts/AuthProvider.tsx @@ -0,0 +1,56 @@ +import { useState, useEffect, useMemo } from "react"; +import { IdToken, useAuth0 } from "@auth0/auth0-react"; +import { jwtDecode } from "jwt-decode"; +import { AuthContext } from "./AuthContext"; + +interface DecodedToken { + exp: number; + iat: number; + iss: string; + sub: string; + aud: string; +} + +interface AuthProviderProps { + children: React.ReactNode; +} +const refreshToken = async ( + getIdTokenClaims: () => Promise, + setToken: (token: string | null) => void +) => { + if (getIdTokenClaims) { + const claims = await getIdTokenClaims(); + if (claims) { + const idToken = claims.__raw; // The raw id_token + localStorage.setItem("auth0.token", idToken); + setToken(idToken); + + try { + const decodedToken: DecodedToken = jwtDecode(idToken); + const expiryTime = decodedToken.exp * 1000; // Convert to milliseconds + const timeoutDuration = expiryTime - Date.now() - 60 * 1000; // Refresh 1 minute before expiry + + setTimeout(() => { + refreshToken(getIdTokenClaims, setToken); + }, timeoutDuration); + } catch (error) { + console.error("Invalid token", error); + } + } + } +}; + +export const AuthProvider: React.FC = ({ children }) => { + const { user, getIdTokenClaims } = useAuth0(); + const [token, setToken] = useState(null); + + useEffect(() => { + if (user && getIdTokenClaims) { + refreshToken(getIdTokenClaims, setToken); + } + }, [user, getIdTokenClaims]); + + const value = useMemo(() => ({ token, user }), [token, user]); + + return {children}; +}; diff --git a/src/contexts/index.ts b/src/contexts/index.ts index ab1352b..a6106b2 100644 --- a/src/contexts/index.ts +++ b/src/contexts/index.ts @@ -1,2 +1,3 @@ export * from "./AuthContext"; +export * from "./AuthProvider"; export * from "./useAuth"; diff --git a/vitest.config.ts b/vitest.config.ts index 69e42c0..6f0c87a 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -18,6 +18,8 @@ export default defineConfig({ "**/index.ts", "**/tests/**", "**/tests-examples/**", + "/playwrightconfig.ts", + "**/types.ts", ], }, },