Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Install vitest and test date utilities and validation functions #10

Merged
merged 6 commits into from
Sep 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,238 changes: 1,230 additions & 8 deletions client/package-lock.json

Large diffs are not rendered by default.

9 changes: 7 additions & 2 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
"preview": "vite preview",
"test": "vitest"
},
"dependencies": {
"@tanstack/react-query": "^5.56.1",
Expand All @@ -27,6 +28,8 @@
},
"devDependencies": {
"@eslint/js": "^9.9.0",
"@testing-library/jest-dom": "^6.5.0",
"@testing-library/react": "^16.0.1",
"@types/node": "^22.5.4",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
Expand All @@ -36,10 +39,12 @@
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
"eslint-plugin-react-refresh": "^0.4.9",
"globals": "^15.9.0",
"jsdom": "^25.0.1",
"postcss": "^8.4.45",
"tailwindcss": "^3.4.11",
"typescript": "^5.6.2",
"typescript-eslint": "^8.5.0",
"vite": "^5.4.1"
"vite": "^5.4.1",
"vitest": "^2.1.1"
}
}
62 changes: 62 additions & 0 deletions client/src/guards/auth-guard.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import {
createMemoryRouter,
RouteObject,
RouterProvider,
} from "react-router-dom";
import { describe, it, expect, vi, Mock, beforeAll } from "vitest";
import { render } from "@testing-library/react";

import { useAuthStore } from "@/lib/stores/auth-store";
import { AppRoutes } from "@/constants/routes";

import AuthGuard from "./auth-guard";

vi.mock("../lib/stores/auth-store.ts", () => ({
useAuthStore: vi.fn(),
}));

const routes: RouteObject[] = [
{
path: AppRoutes.Root,
element: <div>Root</div>,
},
{
element: <AuthGuard />,
children: [
{
path: AppRoutes.SignIn,
element: <div>Sign In</div>,
},
],
},
];

describe("Auth Guard", () => {
let router: ReturnType<typeof createMemoryRouter>;

beforeAll(() => {
(useAuthStore as unknown as Mock).mockReturnValueOnce({
authorized: true,
});

router = createMemoryRouter(routes, {
initialEntries: [AppRoutes.SignIn],
});

render(<RouterProvider router={router} />);
});

it("redirects to the root page when authorized", () => {
expect(router.state.location.pathname).toBe(AppRoutes.Root);
});

it("renders the outlet when not authorized", () => {
(useAuthStore as unknown as Mock).mockReturnValueOnce({
authorized: false,
});

render(<RouterProvider router={router} />);

expect(router);
});
});
122 changes: 122 additions & 0 deletions client/src/guards/private-guard.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import {
createMemoryRouter,
RouteObject,
RouterProvider,
} from "react-router-dom";
import { describe, it, expect, vi, Mock } from "vitest";
import { useQuery } from "@tanstack/react-query";
import { render } from "@testing-library/react";
import "@testing-library/jest-dom";

import { useAuthStore } from "@/lib/stores/auth-store";
import { AppRoutes } from "@/constants/routes";

import PrivateGuard from "./private-guard";

vi.mock("@/lib/stores/auth-store", () => ({
useAuthStore: vi.fn().mockReturnValueOnce({
authorized: false,
setAuthorized: vi.fn(),
}),
}));

vi.mock("@tanstack/react-query", () => ({
useQuery: vi.fn().mockReturnValueOnce({
isLoading: false,
isError: false,
isSuccess: false,
}),
}));

const routes: RouteObject[] = [
{
path: AppRoutes.SignIn,
element: <div>Sign In</div>,
},
{
element: <PrivateGuard />,
children: [
{
path: AppRoutes.Root,
element: <div>Root</div>,
},
],
},
];

describe("Private Guard", () => {
it("redirects to sign-in page when not authorized", () => {
const router = createMemoryRouter(routes, {
initialEntries: [AppRoutes.Root],
});

render(<RouterProvider router={router} />);

expect(router.state.location.pathname).toEqual(AppRoutes.SignIn);
});

it("redirects to sign in page when an error occurs", () => {
const router = createMemoryRouter(routes, {
initialEntries: [AppRoutes.Root],
});

(useAuthStore as unknown as Mock).mockReturnValueOnce({
authenticated: true,
setAuthorized: vi.fn(),
});

(useQuery as Mock).mockReturnValueOnce({
isError: true,
isLoading: false,
isSuccess: false,
});

render(<RouterProvider router={router} />);

expect(router.state.location.pathname).toEqual(AppRoutes.SignIn);
});

it("allows access to root page when authorized", () => {
const router = createMemoryRouter(routes, {
initialEntries: [AppRoutes.Root],
});

(useAuthStore as unknown as Mock).mockReturnValueOnce({
authorized: true,
setAuthorized: vi.fn(),
});

(useQuery as Mock).mockReturnValueOnce({
isError: false,
isLoading: false,
isSuccess: true,
});

render(<RouterProvider router={router} />);

expect(router.state.location.pathname).toEqual(AppRoutes.Root);
});

it("renders loading component when query is loading", () => {
const router = createMemoryRouter(routes, {
initialEntries: [AppRoutes.Root],
});

(useAuthStore as unknown as Mock).mockReturnValueOnce({
authorized: true,
setAuthorized: vi.fn(),
});

(useQuery as Mock).mockReturnValueOnce({
isError: false,
isLoading: true,
isSuccess: false,
});

const { container } = render(<RouterProvider router={router} />);

const loader = container.querySelector("svg");

expect(loader).toBeInTheDocument();
});
});
5 changes: 4 additions & 1 deletion client/src/lib/classes/error-response-class.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
type ErrorResponseType = {
status: number;
message: string;
response: Response;
};

export class ErrorResponse extends Error {
public status: number;
public message: string;
public response: Response;

constructor({ status, message }: ErrorResponseType) {
constructor({ status, message, response }: ErrorResponseType) {
super(message);
this.status = status;
this.message = message;
this.response = response;
}
}
5 changes: 4 additions & 1 deletion client/src/lib/classes/generic-response-class.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
type GenericResponseType = {
message: string;
response: Response;
};

export class GenericResponse {
public message: string;
public response: Response;

constructor({ message }: GenericResponseType) {
constructor({ message, response }: GenericResponseType) {
this.message = message;
this.response = response;
}
}
5 changes: 4 additions & 1 deletion client/src/lib/classes/user-response-class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ import { UserDTO } from "@/lib/DTO/user-dto";
type UserResponseType = {
message: string;
user: UserDTO;
response: Response;
};

export class UserResponse {
public message: string;
public user: UserDTO;
public response: Response;

constructor({ message, user }: UserResponseType) {
constructor({ message, user, response }: UserResponseType) {
this.message = message;
this.user = user;
this.response = response;
}
}
90 changes: 90 additions & 0 deletions client/src/lib/hooks/use-resize.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { describe, it, expect, beforeAll } from "vitest";
import { act, renderHook } from "@testing-library/react";

import { useResize } from "./use-resize";

describe("useResize", () => {
const sm = 640;
const md = 768;
const lg = 1024;
const xl = 1280;
const xl2 = 1536;

beforeAll(() => {
window.innerWidth = 800;
window.innerHeight = 600;
});

it("should initialize with the correct window size", () => {
const { result } = renderHook(() => useResize());
expect(result.current).toEqual({ width: 800, height: 600 });
});

it("should update size on window resize", () => {
const { result } = renderHook(() => useResize());
expect(result.current).toEqual({ width: 800, height: 600 });

act(() => {
window.innerWidth = 1024;
window.innerHeight = 768;
window.dispatchEvent(new Event("resize"));
});

expect(result.current).toEqual({ width: 1024, height: 768 });
});

it("should be small screen", () => {
const { result } = renderHook(() => useResize());

act(() => {
window.innerWidth = sm;
window.dispatchEvent(new Event("resize"));
});

expect(result.current.width).toEqual(sm);
});

it("should be medium screen", () => {
const { result } = renderHook(() => useResize());

act(() => {
window.innerWidth = md;
window.dispatchEvent(new Event("resize"));
});

expect(result.current.width).toEqual(md);
});

it("should be large screen", () => {
const { result } = renderHook(() => useResize());

act(() => {
window.innerWidth = lg;
window.dispatchEvent(new Event("resize"));
});

expect(result.current.width).toEqual(lg);
});

it("should be extra large screen", () => {
const { result } = renderHook(() => useResize());

act(() => {
window.innerWidth = xl;
window.dispatchEvent(new Event("resize"));
});

expect(result.current.width).toEqual(xl);
});

it("should be 2x extra large screen", () => {
const { result } = renderHook(() => useResize());

act(() => {
window.innerWidth = xl2;
window.dispatchEvent(new Event("resize"));
});

expect(result.current.width).toEqual(xl2);
});
});
Loading