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"
)}
>
-
+