Fix tests

This commit is contained in:
Arthur Belleville 2025-04-10 07:29:48 +02:00
parent d5e39c03f0
commit 1b5c56ca9b
No known key found for this signature in database
16 changed files with 212 additions and 242 deletions

View file

@ -1,2 +0,0 @@
import "./login-with-google.css";
export declare function LoginWithGoogle(): import("react/jsx-runtime").JSX.Element;

View file

@ -1 +0,0 @@
export {};

View file

@ -17,10 +17,9 @@ describe("LoginWithGoogle", () => {
render(<LoginWithGoogle />);
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(<LoginWithGoogle />);
const button = screen.getByRole("button", {
name: /continue with google/i,
name: /Continuer avec Google/i,
});
fireEvent.click(button);

View file

@ -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: () => <div>Theme Switcher</div>,
}));
describe("Layout", () => {
it("renders the layout with children", () => {
render(
<BrowserRouter>
{/* Mock SessionProvider just passes children through */}
<Layout>
<div>Test Content</div>
</Layout>
</BrowserRouter>
renderWithProviders(
<Layout>
<div>Test Content</div>
</Layout>
);
// 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(
<BrowserRouter>
<SessionProvider>
<Layout>
<div>Test Content</div>
</Layout>
</SessionProvider>
</BrowserRouter>
renderWithProviders(
<Layout>
<div>Test Content</div>
</Layout>
);
// Check if the side navigation is present
expect(screen.getByRole("navigation")).toBeInTheDocument();
expect(
screen.getByRole("navigation", { name: "Main navigation" })
).toBeInTheDocument();
});
});

View file

@ -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;

View file

@ -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(
<BrowserRouter>
<SessionProvider>
<Routes>
<Route element={<ProtectedRoute />}>
<Route path="/" element={<div>Protected Content</div>} />
</Route>
</Routes>
</SessionProvider>
</BrowserRouter>
renderWithRouter(
<SessionTestProvider>
<Routes>
<Route element={<ProtectedRoute />}>
<Route path="/" element={<div>Protected Content</div>} />
</Route>
</Routes>
</SessionTestProvider>,
{ route: "/" }
);
// Check if loading spinner is present
@ -30,17 +29,16 @@ describe("ProtectedRoute", () => {
});
it("redirects to login when user is not authenticated", async () => {
render(
<BrowserRouter>
<SessionProvider>
<Routes>
<Route element={<ProtectedRoute />}>
<Route path="/" element={<div>Protected Content</div>} />
</Route>
<Route path="/login" element={<div>Login Page</div>} />
</Routes>
</SessionProvider>
</BrowserRouter>
renderWithRouter(
<SessionTestProvider>
<Routes>
<Route element={<ProtectedRoute />}>
<Route path="/" element={<div>Protected Content</div>} />
</Route>
<Route path="/login" element={<div>Login Page</div>} />
</Routes>
</SessionTestProvider>,
{ 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(
<BrowserRouter>
<SessionProvider>
<Routes>
<Route element={<ProtectedRoute />}>
<Route path="/" element={<div>Protected Content</div>} />
</Route>
<Route path="/landing" element={<div>Landing Page</div>} />
</Routes>
</SessionProvider>
</BrowserRouter>
renderWithRouter(
<SessionTestProvider>
<Routes>
<Route element={<ProtectedRoute />}>
<Route path="/" element={<div>Protected Content</div>} />
</Route>
<Route path="/landing" element={<div>Landing Page</div>} />
</Routes>
</SessionTestProvider>,
{ 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(
<SessionTestProvider
testUser={{
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",
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(
<BrowserRouter>
<SessionProvider>
<Routes>
<Route element={<ProtectedRoute />}>
<Route path="/" element={<div>Protected Content</div>} />
</Route>
</Routes>
</SessionProvider>
</BrowserRouter>
role: "authenticated",
updated_at: new Date().toISOString(),
}}
>
<Routes>
<Route element={<ProtectedRoute />}>
<Route path="/" element={<div>Protected Content</div>} />
</Route>
</Routes>
</SessionTestProvider>,
{ route: "/" }
);
// Wait for the loading state to finish
@ -121,20 +106,17 @@ describe("ProtectedRoute", () => {
});
it("uses custom fallback route when provided", async () => {
render(
<BrowserRouter>
<SessionProvider>
<Routes>
<Route element={<ProtectedRoute fallback="/custom-login" />}>
<Route path="/" element={<div>Protected Content</div>} />
</Route>
<Route
path="/custom-login"
element={<div>Custom Login Page</div>}
/>
</Routes>
</SessionProvider>
</BrowserRouter>
renderWithRouter(
<SessionTestProvider>
<Routes>
<Route element={<ProtectedRoute fallback="/custom-login" />}>
<Route path="/" element={<div>Protected Content</div>} />
</Route>
<Route path="/custom-login" element={<div>Custom Login Page</div>} />
<Route path="/landing" element={<div>Landing Page</div>} />
</Routes>
</SessionTestProvider>,
{ route: "/" }
);
// Wait for the loading state to finish

View file

@ -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", () => (
<div>
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-emerald-500" />
<div
role="status"
className="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-emerald-500"
/>
</div>
</div>
))

View file

@ -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(
<BrowserRouter>
renderWithRouter(
<SessionTestProvider>
<Routes>
<Route element={<PublicRoute />}>
<Route path="/login" element={<div>Login Page</div>} />
</Route>
</Routes>
</BrowserRouter>
</SessionTestProvider>,
{ 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(
<BrowserRouter>
it("redirect to home when user is authenticated", async () => {
renderWithRouter(
<SessionTestProvider
testUser={{
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(),
}}
>
<Routes>
<Route element={<PublicRoute />}>
<Route path="/login" element={<div>Login Page</div>} />
</Route>
<Route path="/" element={<div>Home Page</div>} />
</Routes>
</BrowserRouter>
</SessionTestProvider>,
{ 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(
<BrowserRouter>
renderWithRouter(
<SessionTestProvider>
<Routes>
<Route element={<PublicRoute />}>
<Route path="/login" element={<div>Login Page</div>} />
</Route>
</Routes>
</BrowserRouter>
</SessionTestProvider>,
{ route: "/login" }
);
// Wait for the loading state to finish

View file

@ -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", () => (
<div>
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-emerald-500" />
<div
role="status"
className="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-emerald-500"
/>
</div>
</div>
))

View file

@ -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(<SignOutButton />);
// 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(<SignOutButton />);
renderWithRouter(<SignOutButton />);
// Click the button
fireEvent.click(screen.getByRole("button", { name: /se déconnecter/i }));

View file

@ -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(<ThemeSwitcher />);
// 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");

View file

@ -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"
)}
>
<ToggleButton id="light" isIconOnly className="p-1 pr-2">
<ToggleButton
id="light"
isIconOnly
className="p-1 pr-2"
aria-label="light"
>
<svg
className="w-4 h-4 text-slate-500"
fill="none"
@ -50,6 +55,7 @@ export function ThemeSwitcher() {
id="system"
isIconOnly
className="p-1 pl-2 border-l-1 border-l-slate-500/50"
aria-label="system"
>
<svg
className="w-4 h-4 text-slate-500"
@ -69,6 +75,7 @@ export function ThemeSwitcher() {
id="dark"
isIconOnly
className="p-1 pl-2 border-l-1 border-l-slate-500/50"
aria-label="dark"
>
<svg
className="w-4 h-4 text-slate-500"

View file

@ -1,6 +1,6 @@
import { createContext, useContext, useEffect, useState } from "react";
import { Session, User } from "@supabase/supabase-js";
import { supabase } from "@ui/hooks/auth";
import { supabase } from "../hooks/auth";
const SessionContext = createContext<{
session: Session | null;
@ -16,19 +16,9 @@ export const useSession = () => {
return context;
};
type Props = { children: React.ReactNode; testUser?: User };
export const SessionProvider = ({ children, testUser }: Props) => {
const [session, setSession] = useState<Session | null>(
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<Session | null>(null);
useEffect(() => {
const authStateListener = supabase.auth.onAuthStateChange(
@ -48,3 +38,23 @@ export const SessionProvider = ({ children, testUser }: Props) => {
</SessionContext.Provider>
);
};
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 (
<SessionContext.Provider value={{ session }}>
{children}
</SessionContext.Provider>
);
};

View file

@ -21,7 +21,5 @@ export function useGetUser() {
},
});
console.log("data", data);
return { me: data?.user.user_metadata };
}

View file

@ -126,7 +126,6 @@ function Toast({ state, ...props }: ToastProps) {
}
const type = props.toast.content.type;
console.log(props.toast);
return (
<div
{...toastProps}

View file

@ -1,8 +1,20 @@
import { render, RenderResult } from "@testing-library/react";
import { SessionProvider } from "@ui/contexts/SessionContext";
import { SessionTestProvider } from "@ui/contexts/SessionContext";
import { BrowserRouter } from "react-router-dom";
import { ThemeProvider } from "@ui/contexts/ThemeContext";
import { User } from "@supabase/supabase-js";
import userEvent from "@testing-library/user-event";
import { queryClient } from "@ui/lib/api";
import { QueryClientProvider } from "@tanstack/react-query";
export const renderWithRouter = (ui: React.ReactNode, { route = "/" } = {}) => {
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(
<BrowserRouter>
<ThemeProvider>
<SessionProvider
testUser={
opts.user ?? {
id: "123",
app_metadata: {},
aud: "test",
created_at: "2021-01-01",
user_metadata: {
first_name: "John",
last_name: "Doe",
avatar_url: "https://example.com/avatar.jpg",
full_name: "John Doe",
},
<QueryClientProvider client={queryClient}>
<SessionTestProvider
testUser={
opts.user ?? {
id: "123",
app_metadata: {},
aud: "test",
created_at: "2021-01-01",
user_metadata: {
first_name: "John",
last_name: "Doe",
avatar_url: "https://example.com/avatar.jpg",
full_name: "John Doe",
},
}
}
}
>
{ui}
</SessionProvider>
>
{ui}
</SessionTestProvider>
</QueryClientProvider>
</ThemeProvider>
</BrowserRouter>
);