From 1b5c56ca9b52c53ff3c9f5d919bb165ab8a4a068 Mon Sep 17 00:00:00 2001 From: Arthur Belleville Date: Thu, 10 Apr 2025 07:29:48 +0200 Subject: [PATCH] Fix tests --- .../BrandButtons/LoginWithGoogle.d.ts | 2 - .../BrandButtons/LoginWithGoogle.test.d.ts | 1 - .../BrandButtons/LoginWithGoogle.test.tsx | 5 +- ui/src/components/Layout.test.tsx | 52 ++---- ui/src/components/NavigationBar.tsx | 1 + ui/src/components/ProtectedRoute.test.tsx | 166 ++++++++---------- ui/src/components/ProtectedRoute.tsx | 9 +- ui/src/components/PublicRoute.test.tsx | 79 ++++----- ui/src/components/PublicRoute.tsx | 7 +- ui/src/components/SignOutButton.test.tsx | 14 +- ui/src/components/ThemeSwitcher.test.tsx | 14 +- ui/src/components/ThemeSwitcher.tsx | 13 +- ui/src/contexts/SessionContext.tsx | 38 ++-- ui/src/hooks/user.ts | 2 - ui/src/ui-library/toast/toast-region.tsx | 1 - ui/src/utils/testHelpers.tsx | 50 ++++-- 16 files changed, 212 insertions(+), 242 deletions(-) delete mode 100644 ui/src/components/BrandButtons/LoginWithGoogle.d.ts delete mode 100644 ui/src/components/BrandButtons/LoginWithGoogle.test.d.ts diff --git a/ui/src/components/BrandButtons/LoginWithGoogle.d.ts b/ui/src/components/BrandButtons/LoginWithGoogle.d.ts deleted file mode 100644 index 1d5a0ef..0000000 --- a/ui/src/components/BrandButtons/LoginWithGoogle.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -import "./login-with-google.css"; -export declare function LoginWithGoogle(): import("react/jsx-runtime").JSX.Element; diff --git a/ui/src/components/BrandButtons/LoginWithGoogle.test.d.ts b/ui/src/components/BrandButtons/LoginWithGoogle.test.d.ts deleted file mode 100644 index cb0ff5c..0000000 --- a/ui/src/components/BrandButtons/LoginWithGoogle.test.d.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/ui/src/components/BrandButtons/LoginWithGoogle.test.tsx b/ui/src/components/BrandButtons/LoginWithGoogle.test.tsx index 18c3bdc..3174855 100644 --- a/ui/src/components/BrandButtons/LoginWithGoogle.test.tsx +++ b/ui/src/components/BrandButtons/LoginWithGoogle.test.tsx @@ -17,10 +17,9 @@ describe("LoginWithGoogle", () => { render(); const button = screen.getByRole("button", { - name: /continue with google/i, + name: /Continuer avec Google/i, }); expect(button).toBeInTheDocument(); - expect(button).toHaveClass("login-with-google"); }); it("calls loginWithGoogle when clicked", () => { @@ -32,7 +31,7 @@ describe("LoginWithGoogle", () => { render(); const button = screen.getByRole("button", { - name: /continue with google/i, + name: /Continuer avec Google/i, }); fireEvent.click(button); diff --git a/ui/src/components/Layout.test.tsx b/ui/src/components/Layout.test.tsx index 82ea86e..9059ea0 100644 --- a/ui/src/components/Layout.test.tsx +++ b/ui/src/components/Layout.test.tsx @@ -1,39 +1,15 @@ import { render, screen, fireEvent } from "@testing-library/react"; import { Layout } from "@ui/components/Layout"; import { SessionProvider } from "@ui/contexts/SessionContext"; +import { renderWithProviders } from "@ui/utils/testHelpers"; import { BrowserRouter } from "react-router-dom"; -import { vi } from "vitest"; - -// Mock the SessionContext -vi.mock("../../contexts/SessionContext", () => ({ - ...vi.importActual("../../contexts/SessionContext"), - SessionProvider: ({ children }: { children: React.ReactNode }) => children, - useSession: () => ({ - session: { - user: { - user_metadata: { - full_name: "John Doe", - avatar_url: "https://example.com/avatar.jpg", - first_name: "John", - }, - }, - }, - }), -})); - -vi.mock("../ThemeSwitcher", () => ({ - ThemeSwitcher: () =>
Theme Switcher
, -})); describe("Layout", () => { it("renders the layout with children", () => { - render( - - {/* Mock SessionProvider just passes children through */} - -
Test Content
-
-
+ renderWithProviders( + +
Test Content
+
); // Check if the content is rendered @@ -62,7 +38,7 @@ describe("Layout", () => { const menuButton = screen.getByRole("button", { name: /menu/i }); // Verify initial mobile state - const navigation = screen.getByLabelText("Side Navigation"); + const navigation = screen.getByLabelText("Main navigation"); expect(navigation).toHaveClass("-translate-x-full"); expect(navigation).not.toHaveClass("translate-x-0"); @@ -76,17 +52,15 @@ describe("Layout", () => { }); it("renders the side navigation", () => { - render( - - - -
Test Content
-
-
-
+ renderWithProviders( + +
Test Content
+
); // Check if the side navigation is present - expect(screen.getByRole("navigation")).toBeInTheDocument(); + expect( + screen.getByRole("navigation", { name: "Main navigation" }) + ).toBeInTheDocument(); }); }); diff --git a/ui/src/components/NavigationBar.tsx b/ui/src/components/NavigationBar.tsx index 80233cb..b1a46ec 100644 --- a/ui/src/components/NavigationBar.tsx +++ b/ui/src/components/NavigationBar.tsx @@ -31,6 +31,7 @@ import logo from "../assets/icon.jpg"; import { ThemeSwitcher } from "./ThemeSwitcher"; import { useSession } from "../contexts/SessionContext"; import { Text } from "@ui/ui-library/text"; + type NavLinkItem = { isActive?: boolean; } & LinkProps; diff --git a/ui/src/components/ProtectedRoute.test.tsx b/ui/src/components/ProtectedRoute.test.tsx index 4252133..a5a672e 100644 --- a/ui/src/components/ProtectedRoute.test.tsx +++ b/ui/src/components/ProtectedRoute.test.tsx @@ -1,28 +1,27 @@ -import { render, screen, waitFor } from "@testing-library/react"; +import { screen, waitFor } from "@testing-library/react"; import { ProtectedRoute } from "@ui/components/ProtectedRoute"; -import { SessionProvider } from "@ui/contexts/SessionContext"; -import { BrowserRouter, Routes, Route } from "react-router-dom"; -import * as SessionContext from "@ui/contexts/SessionContext"; -import { Session } from "@supabase/supabase-js"; -import { vi } from "vitest"; +import { SessionTestProvider } from "@ui/contexts/SessionContext"; +import { renderWithRouter } from "@ui/utils/testHelpers"; +import { Routes, Route } from "react-router-dom"; describe("ProtectedRoute", () => { beforeEach(() => { - // Clear localStorage before each test - localStorage.clear(); + localStorage.setItem("xtablo-has-seen-landing-page", "true"); + }); + afterEach(() => { + localStorage.removeItem("xtablo-has-seen-landing-page"); }); it("shows loading state initially", () => { - render( - - - - }> - Protected Content} /> - - - - + renderWithRouter( + + + }> + Protected Content} /> + + + , + { route: "/" } ); // Check if loading spinner is present @@ -30,17 +29,16 @@ describe("ProtectedRoute", () => { }); it("redirects to login when user is not authenticated", async () => { - render( - - - - }> - Protected Content} /> - - Login Page} /> - - - + renderWithRouter( + + + }> + Protected Content} /> + + Login Page} /> + + , + { route: "/" } ); // Wait for the loading state to finish @@ -53,17 +51,16 @@ describe("ProtectedRoute", () => { // Set up first-time user scenario localStorage.removeItem("xtablo-has-seen-landing-page"); - render( - - - - }> - Protected Content} /> - - Landing Page} /> - - - + renderWithRouter( + + + }> + Protected Content} /> + + Landing Page} /> + + , + { route: "/" } ); // Wait for the loading state to finish @@ -73,45 +70,33 @@ describe("ProtectedRoute", () => { }); it("renders protected content when user is authenticated", async () => { - // Mock authenticated session - const mockSession: Session = { - access_token: "test-token", - refresh_token: "test-refresh-token", - expires_in: 3600, - token_type: "bearer", - user: { - id: "123", - app_metadata: {}, - user_metadata: { - full_name: "Test User", + renderWithRouter( + ({ - session: mockSession, - })); - - render( - - - - }> - Protected Content} /> - - - - + role: "authenticated", + updated_at: new Date().toISOString(), + }} + > + + }> + Protected Content} /> + + + , + { route: "/" } ); // Wait for the loading state to finish @@ -121,20 +106,17 @@ describe("ProtectedRoute", () => { }); it("uses custom fallback route when provided", async () => { - render( - - - - }> - Protected Content} /> - - Custom Login Page} - /> - - - + renderWithRouter( + + + }> + Protected Content} /> + + Custom Login Page} /> + Landing Page} /> + + , + { route: "/" } ); // Wait for the loading state to finish diff --git a/ui/src/components/ProtectedRoute.tsx b/ui/src/components/ProtectedRoute.tsx index a42904d..eba32db 100644 --- a/ui/src/components/ProtectedRoute.tsx +++ b/ui/src/components/ProtectedRoute.tsx @@ -27,9 +27,9 @@ export const ProtectedRoute = ({ fallback }: ProtectedRouteProps) => { localStorage.getItem("xtablo-has-seen-landing-page") === null; if (isLoading) { status = "loading"; - } else if (!session && isFirstTimeUser) { + } else if (!session?.user && isFirstTimeUser) { status = "should-land-user"; - } else if (!session) { + } else if (!session?.user) { status = "should-redirect"; } else { status = "should-pass"; @@ -41,7 +41,10 @@ export const ProtectedRoute = ({ fallback }: ProtectedRouteProps) => { .with("loading", () => (
-
+
)) diff --git a/ui/src/components/PublicRoute.test.tsx b/ui/src/components/PublicRoute.test.tsx index e1093e4..030c6e6 100644 --- a/ui/src/components/PublicRoute.test.tsx +++ b/ui/src/components/PublicRoute.test.tsx @@ -1,65 +1,55 @@ -import { render, screen, waitFor } from "@testing-library/react"; +import { screen, waitFor } from "@testing-library/react"; import { PublicRoute } from "@ui/components/PublicRoute"; -import { BrowserRouter, Routes, Route } from "react-router-dom"; -import * as SessionContext from "@ui/contexts/SessionContext"; -import { Session } from "@supabase/supabase-js"; -import { vi } from "vitest"; +import { Routes, Route } from "react-router-dom"; +import { + SessionProvider, + SessionTestProvider, +} from "@ui/contexts/SessionContext"; +import { renderWithRouter } from "@ui/utils/testHelpers"; describe("PublicRoute", () => { it("shows loading state initially", () => { - render( - + renderWithRouter( + }> Login Page
} /> - + , + { route: "/login" } ); // Check if loading spinner is present expect(screen.getByRole("status")).toBeInTheDocument(); }); - it("redirects to home when user is authenticated", async () => { - // Mock authenticated session - const mockSession: Session = { - access_token: "test-token", - refresh_token: "test-refresh-token", - expires_in: 3600, - token_type: "bearer", - user: { - id: "123", - app_metadata: {}, - user_metadata: { - full_name: "Test User", - email: "test@example.com", - email_verified: true, - first_name: "Test", - last_name: "User", - business_name: "Test Business", - }, - aud: "authenticated", - created_at: new Date().toISOString(), - email: "test@example.com", - role: "authenticated", - updated_at: new Date().toISOString(), - }, - }; - - vi.spyOn(SessionContext, "useSession").mockImplementation(() => ({ - session: mockSession, - })); - - render( - + it("redirect to home when user is authenticated", async () => { + renderWithRouter( + }> Login Page
} /> Home Page} /> - + , + { route: "/login" } ); // Wait for the loading state to finish @@ -69,14 +59,15 @@ describe("PublicRoute", () => { }); it("renders public content when user is not authenticated", async () => { - render( - + renderWithRouter( + }> Login Page} /> - + , + { route: "/login" } ); // Wait for the loading state to finish diff --git a/ui/src/components/PublicRoute.tsx b/ui/src/components/PublicRoute.tsx index ea70549..83c2fcc 100644 --- a/ui/src/components/PublicRoute.tsx +++ b/ui/src/components/PublicRoute.tsx @@ -17,7 +17,7 @@ export const PublicRoute = () => { let status: "loading" | "should-redirect" | "should-pass" = "loading"; if (isLoading) { status = "loading"; - } else if (session) { + } else if (session?.user) { status = "should-redirect"; } else { status = "should-pass"; @@ -28,7 +28,10 @@ export const PublicRoute = () => { .with("loading", () => (
-
+
)) diff --git a/ui/src/components/SignOutButton.test.tsx b/ui/src/components/SignOutButton.test.tsx index 7fba01c..f56ee3c 100644 --- a/ui/src/components/SignOutButton.test.tsx +++ b/ui/src/components/SignOutButton.test.tsx @@ -1,8 +1,9 @@ -import { render, screen, fireEvent } from "@testing-library/react"; +import { screen, fireEvent } from "@testing-library/react"; import { SignOutButton } from "@ui/components/SignOutButton"; import * as AuthHooks from "@ui/hooks/auth"; import { UseMutationResult } from "@tanstack/react-query"; import { vi } from "vitest"; +import { renderWithRouter } from "@ui/utils/testHelpers"; // Create a mock mutation result const createMockMutationResult = ( @@ -35,22 +36,13 @@ vi.mock("../../hooks/auth", () => ({ })); describe("SignOutButton", () => { - it("renders the sign out button", () => { - render(); - - // Check if the button is rendered with correct text - expect( - screen.getByRole("button", { name: /se déconnecter/i }) - ).toBeInTheDocument(); - }); - it("calls logout function when clicked", () => { const mockLogout = vi.fn(); vi.spyOn(AuthHooks, "useLogout").mockImplementation(() => createMockMutationResult(mockLogout) ); - render(); + renderWithRouter(); // Click the button fireEvent.click(screen.getByRole("button", { name: /se déconnecter/i })); diff --git a/ui/src/components/ThemeSwitcher.test.tsx b/ui/src/components/ThemeSwitcher.test.tsx index b43afcf..1d256e3 100644 --- a/ui/src/components/ThemeSwitcher.test.tsx +++ b/ui/src/components/ThemeSwitcher.test.tsx @@ -3,9 +3,9 @@ import { ThemeSwitcher } from "@ui/components/ThemeSwitcher"; import * as ThemeContext from "@ui/contexts/ThemeContext"; import { vi } from "vitest"; -// Mock the ThemeProvider -vi.mock("../../contexts/ThemeContext", () => ({ - ...vi.importActual("../../contexts/ThemeContext"), +// Mock the ThemeProvider and useTheme hook +vi.mock("@ui/contexts/ThemeContext", () => ({ + ...vi.importActual("@ui/contexts/ThemeContext"), ThemeProvider: ({ children }: { children: React.ReactNode }) => children, useTheme: () => ({ theme: "light", @@ -21,9 +21,9 @@ describe("ThemeSwitcher", () => { expect(screen.getByText("Thème: Clair")).toBeInTheDocument(); // Check if all theme buttons are present - expect(screen.getByRole("button", { name: /light/i })).toBeInTheDocument(); - expect(screen.getByRole("button", { name: /system/i })).toBeInTheDocument(); - expect(screen.getByRole("button", { name: /dark/i })).toBeInTheDocument(); + expect(screen.getByRole("radio", { name: /light/i })).toBeInTheDocument(); + expect(screen.getByRole("radio", { name: /system/i })).toBeInTheDocument(); + expect(screen.getByRole("radio", { name: /dark/i })).toBeInTheDocument(); }); it("changes theme when a different theme button is clicked", () => { @@ -36,7 +36,7 @@ describe("ThemeSwitcher", () => { render(); // Click the dark theme button - fireEvent.click(screen.getByRole("button", { name: /dark/i })); + fireEvent.click(screen.getByRole("radio", { name: /dark/i })); // Verify that setTheme was called with 'dark' expect(setTheme).toHaveBeenCalledWith("dark"); diff --git a/ui/src/components/ThemeSwitcher.tsx b/ui/src/components/ThemeSwitcher.tsx index 6ae271b..014c44f 100644 --- a/ui/src/components/ThemeSwitcher.tsx +++ b/ui/src/components/ThemeSwitcher.tsx @@ -1,7 +1,7 @@ import { ToggleButtonGroup, ToggleButton } from "../ui-library/button"; import { twMerge } from "tailwind-merge"; -import { useTheme } from "../contexts/ThemeContext"; -import { Text } from "../ui-library/text"; +import { useTheme } from "@ui/contexts/ThemeContext"; +import { Text } from "@ui/ui-library/text"; const translation = { light: "Clair", @@ -31,7 +31,12 @@ export function ThemeSwitcher() { "border border-gray-700/80" )} > - + { return context; }; -type Props = { children: React.ReactNode; testUser?: User }; -export const SessionProvider = ({ children, testUser }: Props) => { - const [session, setSession] = useState( - testUser - ? { - user: testUser, - access_token: "test_access_token", - refresh_token: "test_refresh_token", - expires_in: 3600, - token_type: "Bearer", - } - : null - ); +type Props = { children: React.ReactNode }; +export const SessionProvider = ({ children }: Props) => { + const [session, setSession] = useState(null); useEffect(() => { const authStateListener = supabase.auth.onAuthStateChange( @@ -48,3 +38,23 @@ export const SessionProvider = ({ children, testUser }: Props) => { ); }; + +export const SessionTestProvider = ({ + children, + testUser, +}: Props & { testUser?: User }) => { + const session = testUser + ? { + user: testUser, + access_token: "test_access_token", + refresh_token: "test_refresh_token", + expires_in: 3600, + token_type: "Bearer", + } + : null; + return ( + + {children} + + ); +}; diff --git a/ui/src/hooks/user.ts b/ui/src/hooks/user.ts index aad0b20..e812de9 100644 --- a/ui/src/hooks/user.ts +++ b/ui/src/hooks/user.ts @@ -21,7 +21,5 @@ export function useGetUser() { }, }); - console.log("data", data); - return { me: data?.user.user_metadata }; } diff --git a/ui/src/ui-library/toast/toast-region.tsx b/ui/src/ui-library/toast/toast-region.tsx index bf0970d..3bfa42b 100644 --- a/ui/src/ui-library/toast/toast-region.tsx +++ b/ui/src/ui-library/toast/toast-region.tsx @@ -126,7 +126,6 @@ function Toast({ state, ...props }: ToastProps) { } const type = props.toast.content.type; - console.log(props.toast); return (
{ + window.history.pushState({}, "Test page", route); + + return { + user: userEvent.setup(), + ...render(ui, { wrapper: BrowserRouter }), + }; +}; export const renderWithProviders = ( ui: React.ReactNode, @@ -11,24 +23,26 @@ export const renderWithProviders = ( return render( - + - {ui} - + > + {ui} + + );